主题
Vue 的基本用法及模板语法与Vue实例(Lesson2 2/3)
本页对应 Lesson02,共 5 段视频,约 1 小时 17 分。
学习目标
- 能够创建
Vue实例并挂载到指定元素,正确使用插值表达式{{}}输出数据,并说明相同实例挂载到不同元素、不同实例挂载到不同元素时的表现。 - 能够使用
v-html与v-text渲染内容,并区分二者在内容解析与显示方式上的差异。 - 能够使用
@click.stop或事件对象阻止冒泡,并串联.stop、.prevent、.once等修饰符实现组合事件控制。 - 能够使用键盘修饰符(如
.enter、.13、.a、.left、.ctrl等)监听指定按键或组合键,并说明按键别名与键码用法的等价关系。 - 能够解释
JSONP的跨域请求原理,手写动态script标签发起GET请求,定义并调用全局回调函数。 - 能够应用
JSONP调用第三方接口,并通过动态生成回调名、加载后清理script与全局回调等方式避免全局污染。
知识点
模板语法与声明式绑定
为什么需要:直接操作 DOM 写页面,数据一变就要手动找节点、改内容,逻辑容易散。Vue 的模板语法允许声明式地把 DOM 绑定到实例数据上。
怎么想:先有数据,再让模板“描述”数据和视图的关系;Vue 编译模板并配合响应系统,计算最少需要更新什么。
怎么做:在 data 中声明数据,在模板里用插值或指令绑定。笔记指出:Vue 模板是合法 HTML,底层会编译成虚拟 DOM 渲染函数。
易错点:模板里绑定的必须是实例中真实存在的数据,不能临时写一个数字当数据源。
v-model 双向绑定与默认值
为什么需要:表单既要显示数据,又要收集用户输入,手动监听 input 再赋值很啰嗦。
怎么想:v-model 实现视图和数据双向绑定:视图改变驱动数据,数据改变驱动视图。
怎么做:
html
<input v-model="city">js
data() {
return { city: '上海' }
}调试时把 v-model 写成绑定数字 3,页面编译失败;原因是 3 不是 data 中声明的数据。若只是给输入框一个默认值,不应滥用 v-model,可用 value 属性。
易错点:v-model 绑定的是实例数据;只给默认值用 value;要收集数据再用 v-model。
事件修饰符 stop:阻止冒泡
为什么需要:点击按钮时,事件会从里向外冒泡,可能同时触发外层区域的事件。
怎么想:先观察点击按钮、红色区域、黄色区域分别触发谁,再决定是否阻止冒泡。
怎么做:传统写法可用 e.stopPropagation();Vue 可直接加 .stop:
html
<button @click.stop="inner">按钮</button>易错点:最外层通常没有“再往外冒泡”的问题,加不加要按结构判断;不要把所有点击都无脑加 .stop。
once、prevent 与修饰符串联
为什么需要:有些事件只应执行一次,有些要阻止表单提交、链接跳转等默认行为。
怎么想:.once 控制触发次数,.prevent 阻止默认行为,多个修饰符可串联同时生效。
怎么做:
html
<a href="https://www.baidu.com" @click.prevent.once="fn">搜索</a>调试中第一次点击弹出提示且不跳转,第二次因 .once 已失效,默认跳转就发生了。
易错点:.once 只作用一次;.prevent 不是阻止冒泡,别和 .stop 混用;串联时注意顺序和实际效果。
键盘修饰符
为什么需要:搜索框常要判断回车、方向键、组合键,手写 keyCode === 13 可读性差。
怎么想:Vue 允许写键名或键码,也可写组合键。
怎么做:
html
<input @keyup.enter="search">
<input @keyup.13="search">
<input @keyup.left="moveLeft">
<input @keyup.ctrl.c="copy">转写中验证了:回车可写 .enter 或 .13,A 可写 .a 或 .65,左箭头 .left 或 .37,空格 .space 或 .32;组合键如 .ctrl.c 需要同时按下才触发。
易错点:单独按 Ctrl 不一定触发组合键逻辑;键名和键码要写对。
JSONP 复习:动态 script 与回调
为什么需要:Ajax 受同源策略限制,协议、域名、端口任一不同就可能跨域;JSONP 是借 script 标签请求数据的方式。
怎么想:JSONP 只支持 GET。服务端返回“函数名(数据)”的 JS 代码,页面提前准备同名全局函数接收数据。
怎么做:
js
const script = document.createElement('script')
script.src = 'http://127.0.0.1/info2?callback=fn'
script.onload = () => {
document.body.removeChild(script)
}
document.body.appendChild(script)
window.fn = function (data) {
console.log(data)
}调试时只创建 script 没插入 body,网络无反应;插入后请求发出。若服务端返回对象字面量,直接当 JS 执行会报错,必须返回函数调用。
易错点:回调函数写死容易覆盖别人,可用随机函数名挂到 window,用完删除;服务端要用 req.query.callback 拼接回调名;JSONP 不是 Ajax 的万能替代。
JSONP 应用中的地址与参数
为什么需要:真实接口参数很多,不能全抄。
怎么想:保留关键词、回调名等必要参数,删掉无关参数做验证。百度示例中回调参数常见为 cb,京东示例中回调名可能固定。
怎么做:把接口地址放进 script.src,回调函数名与服务端约定一致;加载完移除 script。
易错点:不要以为所有接口都叫 callback;跨域判断看协议、域名、端口三者是否完全一致。
本节目标与代码入口
本节要弄清一件事:vm 就是 Vue 实例。后续模板、事件、数据绑定都围绕这个实例展开。为什么需要先创建实例?因为没有 Vue 构造函数,就没有 vm,配置对象里的 data、methods 也无处挂载。怎么想:先引入核心模块,再用 new Vue({...}) 生成实例。怎么做:在 head 中用 script 引入 lib/vue.js,再在页面底部写实例代码。易错点:引入路径要正确;先引入 Vue 再写 new Vue;不要把配置对象写成空参数。下面保留本节完整代码,便于对照调试。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--1- 引入核心模块,提供了构造函数Vue -->
<script src="lib/vue.js"></script>
</head>
<body>
</body>
<script>
// 1- vm即是Vue实例。
// 2- Vue构造函数接收一个配置对象。
// 3- 配置对象中可以设置data属性(类型是一个对象)
// 4- data对象的属性会作为vm的实例属性(记住)。
// 5- 建议将方法放置到methods属性中(类型也是一个对象)
// const vm = new Vue({
// // 实例中的数据
// data:{
// a:1,
// b:2,
// c:{
// userName:"zhangsan"
// },
// d:[1,2,3]
// },
// // 定义方法
// methods:{
// fn(){
//
// }
// }
// });
// console.log(vm);
// 关于data的特点:
// 1- data中的属性会作为Vue实例中的属性
// 2- data的属性值发生改变,那么Vue实例中对应的属性值也会发生改变
// 3- Vue实例中的属性值发生改变,那么data中的属性值也会发生改变。
const data = {
a:1
}
const vm = new Vue({
data
});
data.a = 100;
vm.a = 200;
console.log(vm.a,data.a);
</script>
</html>Vue构造函数与配置对象
为什么需要配置对象?因为 new Vue() 需要一份“说明书”,告诉 Vue 实例里有哪些数据、哪些方法。怎么想:把配置对象理解为一个普通对象,data 管数据,methods 管方法。怎么做:写成 new Vue({ data: {...}, methods: {...} }),并赋给 const vm。易错点:methods 是对象,不是函数数组;方法要写在 methods 里面。转写稿里口述的“mess”应纠正为 methods,并且写方法时要注意缩进和格式化,避免函数写到对象外面导致不生效。调试时可先 console.log(vm) 看实例结构。
data属性会成为实例属性
为什么需要记住这一点?因为页面和代码通常直接通过 vm.a、vm.c.userName 访问数据,而不是绕回 vm.data.a。怎么想:data 是实例的数据源,Vue 会把其中的属性代理到实例上。怎么做:如笔记中 data:{a:1,b:2,c:{userName:"zhangsan"},d:[1,2,3]},创建后可用 vm.a、vm.c.userName、vm.d 观察。易错点:不要把 data 只当成普通对象而忘记它会挂到 vm 上;嵌套对象仍按对象层级访问;调试时用 console.log(vm) 查看,不要凭感觉猜。
data与实例属性的双向同步调试
为什么需要理解同步?因为这是后续响应式学习的起点:改数据会影响实例,改实例也会回到数据。怎么想:data 和 vm 上的同名属性指向同一份数据,所以最后谁后改,谁的值生效。怎么做:按笔记运行 const data={a:1}; const vm=new Vue({data}); data.a=100; vm.a=200; console.log(vm.a,data.a);。推导过程:data.a=100 先让两边变为 100;随后 vm.a=200 又把两边改为 200;因此控制台最后输出 200 200。易错点:不要颠倒执行顺序后仍按旧值判断;console.log 要放在两次赋值之后;转写稿中“微杠model”是 v-model 的口误,本节先不展开,当前只抓住实例与 data 的同步关系。
本节目标与转写稿校正
本节只解决一个问题:Vue 实例如何挂载。转写稿大量内容在讲 v-model、stop、once、键盘修饰符和 JSONP,和“挂载”不直接对应;其中同音字先纠正:“微杠 modelel”应为 v-model,“真P/真森P”应为 JSONP,“淘/套”应为 stop。可借用的调试提醒是:先引入 vue.js 再 new Vue,写代码要细心、格式化;挂载排查也按这个顺序走。
挂载是什么
为什么需要:new Vue 只创建实例,实例还不知道管理页面哪一块 DOM。没有 el,模板不会编译,{{str}} 会原样显示。
怎么想:把 el 理解为“Vue 实例的作用边界”,边界内才是 Vue 模板。
怎么做:new Vue({ el:"#root", data:{...} }),挂载后 #root 成为 Vue 模板。
易错点:笔记代码里所有 el 都被注释了,直接运行不会挂载;调试时先取消 el:"#root" 这一行。
el 选择器写法与选择建议
为什么需要:el 支持多种选择器,写法不同会影响可维护性。
怎么想:优先用 id,唯一且明确;class 和标签选择器可能误伤其他元素,所以笔记标注“不建议”。
怎么做:可用 el:"#root",也可用 el:document.querySelector("#root");.root、div 写法保留在注释中对照。
易错点:el:"body"、el:"html" 不允许,因为会破坏页面根结构;选择器写错时要先检查 DOM 是否已存在、id 是否拼错。
模板中访问实例数据
为什么需要:模板要渲染 data 中的数据,必须理解表达式的作用域。
怎么想:Vue 模板表达式运行在实例上下文里,data 属性会被代理到实例上。
怎么做:{{str}} 直接访问;{{this.str}} 也能访问;{{vm.str}} 访问 data 里的嵌套对象 vm。
易错点:笔记注释“模块中可以使用 this”应为“模板中”;模板里不建议写 this。另外不要把外层 const vm 和 data 里的 vm 混淆:{{vm.str}} 取的是 data.vm.str,输出 "vm"。
data 嵌套与调试
为什么需要:演示嵌套对象访问,同时暴露同名变量容易看错的问题。
怎么想:data 里可以放对象,模板用点语法访问。
怎么做:data:{ str:"...", vm:{ str:"vm" } }。调试时可在控制台分别看外层实例 vm.str、vm.vm.str。
易错点:同名 vm 多时先分清“外层实例”和“data 属性”;若页面仍显示 {{str}},按顺序查:lib/vue.js 是否加载、new Vue 是否在引入之后、el 是否取消注释、选择器是否匹配。
完整代码
下面保留笔记原代码;运行时需取消一个 el 注释。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div class="root" id="root">
<h1>{{str}}</h1>
<h1>{{this.str}}</h1>
<h1>{{vm.str}}</h1>
</div>
</body>
<script>
// 注意重点:在Vue模板中可以直接使用Vue实例中的属性或方法(不需要使用this,不需要使用this)
// 注意重点:模块中可以使用this(不建议),说明模块所处的环境上下文中的this指向的是Vue实例。
// 挂载(Vue实例挂载):指定Vue实例应用的范围(元素)
const vm = new Vue({
// 将ID为root的元素作为Vue实例挂载的元素(挂载的元素自此可以称为Vue模板)
// el:document.querySelector("#root"),
// 将ID为root的元素作为Vue实例挂载的元素(挂载的元素自此可以称为Vue模板)
// el:"#root",
// 将class为root的元素作为Vue实例挂载的元素( 不建议)
// el:".root",
// 将标签名字为div的元素作为Vue实例挂载的元素( 不建议)
// el:"div",
// el:"body",// 不允许
// el:"html",// 不允许
data:{
str:"我现在开始学习Vue,第一步要进行挂载",
vm:{
str:"vm"
}
}
})
</script>
</html>现象复现:第二个 div 不会变成 one
先保留本节笔记代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div>
<h1>{{str}}</h1>
</div>
<div>
<h1>{{str}}</h1>
</div>
</body>
<script>
// 只会将第一个家客户条件的元素作为挂载元素
new Vue({
el:"div",
data:{
str:"one"
}
})
</script>
</html>运行后,第一个 div 里的 {{str}} 被编译成 one,第二个 div 里的 {{str}} 仍然原样显示。笔记注释里的“家客户条件”是转写同音误写,应理解为“符合条件的元素”。
为什么需要:很多初学者会把 el:"div" 理解成“页面上所有 div 都归这个 Vue 实例管”,于是误以为两个 div 都会渲染。
怎么想:el 本质上是一个选择器,Vue 2 内部按选择器找挂载元素,找到第一个就停止。
怎么做:把 el 写成唯一选择器,例如 el:"#app",并把要渲染的内容放进同一个根容器。
易错点:不要用 div、h1 这类宽泛选择器作为 el;页面上有多个匹配项时,只有第一个会被接管。
为什么只挂载第一个:单实例只有一个挂载点
为什么需要:理解“一个 Vue 实例对应一个挂载点”,才能解释第二个 div 为什么不渲染。
怎么想:new Vue({ el:"div" }) 不是“批量绑定”,而是“找到第一个 div 并接管它”。第二个 div 没有被实例接管,所以模板语法不会被编译。
怎么做:如果两个区域都要用同一份数据,用一个外层容器包住它们:
html
<div id="app">
<div><h1>{{str}}</h1></div>
<div><h1>{{str}}</h1></div>
</div>js
new Vue({
el: "#app",
data: { str: "one" }
})易错点:不要写成两个并列根节点再指望一个实例全部接管;Vue 2 的挂载点应尽量唯一、明确。
多个区域都要渲染怎么办:拆实例或合并根容器
为什么需要:实际页面可能确实有多个独立区域。
怎么想:先判断它们是否共享同一份数据。共享就合并到一个根容器;不共享就分别创建实例。
怎么做:分别挂载时给不同选择器:
html
<div id="app1"><h1>{{str}}</h1></div>
<div id="app2"><h1>{{str}}</h1></div>js
new Vue({ el: "#app1", data: { str: "one" } })
new Vue({ el: "#app2", data: { str: "two" } })易错点:两个实例的 el 不能指向同一个元素;否则后挂载的实例会覆盖或产生冲突。
调试与易错点:先看“有没有被挂载”,再看“数据有没有”
转写稿里有一段类似排查过程:v-model 写错时先出现编译失败,原因是模板绑定找不到 data 中声明的数据;正确做法是先看报错、再检查 data 字段。这个顺序可以迁移到本节:第二个 div 不渲染时,不要先怀疑 str 的值,而要先确认它有没有被 Vue 实例接管。
为什么需要:模板不渲染可能来自“没挂载”“没编译”“数据不存在”三类原因。
怎么想:第一个 div 正常、第二个原样显示,说明数据和实例本身没问题,问题在挂载范围。
怎么做:把 el 改成唯一 id,或把两个 div 放进同一个 #app 根容器,再刷新观察。
易错点:不要用“再写一个 new Vue({el:"div"})”去补第二个,因为它仍然会匹配第一个 div;应使用不同选择器或合并根容器。
本节核心结论:相同 Vue 实例不会自动挂载到所有匹配元素;el 选择器只接管第一个匹配到的挂载元素。
本节目标与代码全貌
本节要解决的问题是:同一个页面里,能不能创建多个 Vue 实例,并让它们分别挂载到不同元素上?答案是可以。下面保留本节笔记中的完整代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="one">
<h1>{{str}}</h1>
</div>
<div id="two">
<h1>{{str}}</h1>
</div>
</body>
<script>
// 同一个页面,支持多个实例挂载至不同元素中。
new Vue({
el:"#one",
data:{
str:"one"
}
})
new Vue({
el:"#two",
data:{
str:"two"
}
})
</script>
</html>这段代码里有两个挂载点 #one、#two,也有两个 new Vue。每个实例只负责自己 el 指定的区域,因此页面最终会分别显示 one 和 two。
知识点一:同一页面可以创建多个 Vue 实例
为什么需要:实际页面不一定只有一个完全统一的 Vue 根实例。比如页面中不同区域需要各自管理数据,或者旧页面逐步接入 Vue 时,就可能出现多个实例分别接管不同区域的情况。多个实例可以让数据边界更清楚,避免所有内容都挤在同一个 data 里。
怎么想:不要把它理解成“一个页面只能有一个 Vue”。这里的关键是“实例”和“挂载点”的对应关系:一个实例通过 el 找到一个页面元素,然后只在这个元素内部进行模板解析和数据绑定。
怎么做:写两个 new Vue,分别设置不同的 el,再分别给 data.str 不同值。代码中第一个实例挂到 #one,第二个挂到 #two,所以两个 h1 中的 {{str}} 会各自读取自己实例的数据。
易错点:两个实例的 el 不要写成同一个选择器;#one 和 #two 的拼写要一致;不要以为第二个实例会自动继承第一个实例的 data。课堂转写里反复提醒“写代码的时候细心一点,该缩进缩进,写完格式化”,这里就体现在两个 el、两个 data 不要抄错。
知识点二:el 挂载点必须与页面元素一一对应
为什么需要:Vue 实例需要知道“我管哪一块 DOM”。el 就是挂载入口。如果没有对应元素,或者对应到了错误元素,模板就不会按预期渲染。
怎么想:先看页面结构:<div id="one"> 和 <div id="two"> 是两个并列的容器。再看实例:第一个 el:"#one",第二个 el:"#two"。这是一一对应关系,不是嵌套关系。
怎么做:在 HTML 中保留两个 id 不同的 div,在脚本中按 id 选择器分别挂载。挂载后,#one 内部的 {{str}} 由第一个实例负责,#two 内部的 {{str}} 由第二个实例负责。
易错点:id 重复会导致选择器命中不确定;el 写成 "one" 而不是 "#one" 时,含义会变成标签选择器而不是 id 选择器;把脚本放到挂载元素之前,也可能因为元素尚未出现在 DOM 中而挂载失败。本节代码把脚本放在 body 后面,就是为了让挂载点先存在。
知识点三:每个实例的 data 是独立的
为什么需要:多个实例存在的意义之一,就是让不同区域的数据互不干扰。如果两个区域共用同一份数据,那么分开实例就没有明显价值。
怎么想:第一个实例的 data.str 是 "one",第二个实例的 data.str 是 "two"。虽然属性名都叫 str,但它们属于不同实例,不是同一个对象。
怎么做:分别在两个 new Vue 的 data 中声明 str。模板里都写 {{str}},但 Vue 会在各自挂载范围内查找所属实例的数据,所以显示结果不同。
易错点:不要在一个实例的模板里直接访问另一个实例的 data;也不要把两个实例的 data 写成同一个外部对象后再修改,这样容易造成数据联动。初学阶段按笔记写法,各自在 data 中声明最清楚。
知识点四:插值表达式只在所属挂载元素内生效
为什么需要:Vue 的模板编译是有范围的。实例只编译自己挂载元素内部的模板,不会随意接管整个页面。
怎么想:{{str}} 写在 #one 里,就由挂载 #one 的实例解析;写在 #two 里,就由挂载 #two 的实例解析。两个 {{str}} 看起来一样,但所属实例不同。
怎么做:保留两个 h1 中的 {{str}},分别放在两个 div 内部。运行后应看到第一个区域显示 one,第二个区域显示 two。
易错点:如果把 {{str}} 写到两个挂载 div 的外面,它不属于任何一个实例的挂载范围,就不会按实例数据渲染。另一个常见问题是 Vue 脚本路径写错,比如 lib/vue.js 不存在,页面会直接报错或无法渲染;这时先检查控制台和资源路径。
调试与易错点汇总
本节代码短,调试时按顺序检查即可:第一,确认 lib/vue.js 能正常加载;第二,确认页面中确实存在 #one 和 #two;第三,确认两个 new Vue 的 el 分别对应这两个 id;第四,确认两个 data.str 分别是 "one" 和 "two";第五,确认 {{str}} 写在对应挂载元素内部。课堂转写中提到的“格式化、缩进、细心”同样适用于这里,因为多实例代码最容易错的地方就是复制后忘记改 el 或忘记改 data。
小结
同一页面支持多个 Vue 实例挂载到不同元素。写的时候抓住三件事:el 一一对应挂载点,data 各自独立,插值表达式只在所属挂载范围内生效。这样两个实例就能互不干扰地分别渲染 one 和 two。
本节目标:理解 {{}} 插值表达式
插值表达式的作用,是把 Vue 实例中的数据、表达式结果渲染到模板里。为什么需要它?因为如果不用 Vue,我们往往要手动 querySelector 再改 innerHTML;用了 {{}},模板只描述“这里显示什么”,数据变化由 Vue 驱动视图。怎么想?把 {{}} 理解成“模板中的 JS 表达式出口”:里面写变量、字面量、三元表达式、函数调用都可以。怎么做?先写 new Vue({ el, data, methods }),再在 #root 内用 {{num}}、{{fn()}} 等。易错点:{{}} 里是表达式,不是语句,不能写 var、if;模板里用到的数据要在 data 中声明。转写稿开头提到 v-model 绑定了一个未在 data 中声明的值导致编译失败,这正提醒我们:模板绑定必须先有实例数据来源。转写稿中“微杠 modelel”应纠正为 v-model。
基本数据渲染:变量与字符串字面量
为什么需要?最常见需求是把 data 中的数字、字符串显示出来。怎么想?{{num}} 取的是实例属性 num;{{"str"}} 取的是字符串字面量。怎么做?在 data 中写 num:1、str:"师法魔级超",模板写 <p>{{num}}</p>、<p>{{str}}</p>、<p>{{"str"}}</p>。易错点:{{str}} 和 {{"str"}} 不是一回事,前者输出变量值,后者输出字符串 str;变量名拼错时不要猜,先看控制台报错,再检查 data。转写稿里反复强调“写程序细心一点、写完格式化”,这同样适用于模板表达式排错。
布尔、数字、对象、数组与方法
为什么需要?插值不只显示字符串,还要显示状态、列表、对象和方法结果。怎么想?笔记总结说:插值支持字符串、数字、布尔、对象、数组、方法;但数组不支持直接展开,对象可以输出。怎么做?写 {{1}}、{{false}}、{{arr}}、{{obj}}、{{fn}}、{{fn()}}。易错点:{{fn}} 显示函数本身,{{fn()}} 才执行并显示返回值;fn() 里若用了 this.str,要确保 str 在 data 中;不要期待数组像 React 那样直接展开成多个节点。对象虽可输出,但不要把它当“展开成属性列表”。
null、undefined 与表达式组合
为什么需要?真实数据可能暂时为空,模板要能安全处理。怎么想?笔记总结:undefined、null 不会进行任何输出。怎么做?直接写 <p>{{null}}</p>、<p>{{undefined}}</p> 观察结果;用三元写 {{sex===1?"男":"女"}};用链式调用写 {{str.split("").reverse().join("")}}。易错点:不要以为会显示文字 null;链式调用要保证 str 是字符串,否则运行报错;表达式过长时可放到 methods 中,如 fn(),模板保持清晰。
<template> 包裹标签
笔记总结5指出:Vue 中包裹标签为 <template></template>。为什么需要?当多个元素需要同一层逻辑包裹,但不想额外生成真实 DOM 时,可用 template。怎么想?它更像“不可见包裹层”,自身不渲染,内容参与渲染。怎么做?在模板中写 <template>...</template> 包住内容。易错点:本节代码里的 <template></template> 是空的,不会输出任何内容;不要把它当普通 div;实际使用时注意 Vue 版本和根模板限制,避免把无关内容混入。
完整示例代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="root">
<!--将Vue实例下的属性num进行渲染-->
<p>{{num}}</p>
<!--将Vue实例下的属性str进行渲染-->
<p>{{str}}</p>
<!--将字符串进行渲染-->
<p>{{"str"}}</p>
<!--将数字进行渲染-->
<p>{{1}}</p>
<!--将Vue实例下的属性bol进行渲染-->
<p>{{bol}}</p>
<!--将布尔值进行渲染-->
<p>{{false}}</p>
<!--将Vue实例下的属性fn进行渲染-->
<p>{{fn}}</p>
<!--将Vue实例下的fn函数运行的结果进行渲染-->
<p>{{fn()}}</p>
<!--将Vue实例下的属性arr进行渲染-->
<p>{{arr}}</p>
<!--将Vue实例下的属性obj进行渲染-->
<p>{{obj}}</p>
<p>{{null}}</p>
<p>{{undefined}}</p>
<p>{{sex===1?"男":"女"}}</p>
<p>{{str.split("").reverse().join("")}}</p>
<template></template>
</div>
</body>
<script>
// 总结1:插值表达式支持字符串,数字,布尔,对象,数组,方法
// 总结2:undefined,null不会进行任何输出(与React相同)
// 总结3:数组不支持直接展开(React支持)
// 总结4:支持输出对象(React不支持)
// 总结5:Vue中包裹标签为<template></template>
new Vue({
el:"#root",
data:{
num:1,
str:"师法魔级超",
bol:true,
arr:[1,2,3,4],
sex:1,
obj:{
userName:"zhangsan"
}
},
methods:{
fn(){
return this.str.split("").reverse().join("");
}
}
})
</script>
</html>调试提醒与转写稿说明
本节笔记没有图片,核心就是上面的 HTML 代码。转写稿主要讨论事件修饰符、键盘修饰符和 JSONP,并没有对 {{}} 做专门推导,因此不能把 .stop、.prevent、.once、键盘修饰符等内容硬套到本节。可借用的是调试习惯:代码要格式化、绑定数据要先声明、报错先看控制台。转写稿中的“真P/真森P/JNP”应纠正为 JSONP,“微杠 modelel”应纠正为 v-model,但这些不是本节插值表达式的主体。学习本节时,先把 data、methods 和 {{}} 的对应关系跑通,再观察 null、undefined、数组、对象、函数调用的实际输出差异。
指令是什么
指令是带有 v- 前缀的特殊属性,值通常是单个 JavaScript 表达式(v-for 例外)。它的职责是:表达式值改变时,把连带影响响应式地作用于 DOM。指令分内置指令和自定义指令。要记住一句话:组件是对标签的扩展,指令是对属性的扩展。常用插件入口可保留:极简插件:https://chrome.zzzmh.cn/#/index
v-model 的绑定与默认值
为什么需要:表单要做视图和数据双向绑定,用户改输入框要更新数据,数据变也要驱动视图。怎么想:v-model 绑定的是 Vue 实例 data 里声明的数据,不是随便写个字面量。怎么做:先在 data 中声明,再写 v-model。转写里把 v-model 写成绑定 3,编译失败,因为 data 中找不到这个数据;如果只是想给输入框一个默认值,不需要双向收集,直接用 value 即可。易错点:v-model 后面不能写未声明的数字或字符串;要收集数据就用 data 属性,要单纯默认值用 value。
html
<input v-model="city">
<input value="深圳">.stop 阻止冒泡
为什么需要:按钮在红色、黄色区域内部,点击按钮会由里向外冒泡,触发外层点击事件。怎么想:传统写法在事件对象上调用 e.stopPropagation();Vue 提供修饰符 .stop。怎么做:给内层事件加 @click.stop。转写中先给按钮、红区、黄区加点击事件,点击按钮会同时触发外层,加 .stop 后只执行按钮自己的处理。易错点:最外层通常不用加,因为它外面没有可冒泡的父级;修饰符要加在真正需要阻止冒泡的那一层。
html
<button @click.stop="inner">按钮</button>.prevent、.once 与串联
为什么需要:表单提交、链接跳转有默认行为;有些事件只想执行一次。怎么想:.prevent 阻止默认行为,.once 让事件只作用一次,多个修饰符可以串联。怎么做:转写里给表单按钮加 @click.prevent,点击搜索不再跳转;加 .once 后第一次弹窗,第二次不再拦截,表单就提交了。串联如 @click.prevent.stop.once,表示同时阻止默认行为、阻止冒泡、只执行一次。易错点:.once 只保证该监听只执行一次,不是“永远禁用默认行为”;串联时每个修饰符都各司其职。
html
<button @click.prevent.stop.once="fn">搜索</button>键盘修饰符
为什么需要:搜索框按回车才发请求,或判断方向键、组合键。怎么想:以前写 e.keyCode === 13;Vue 可直接写键名或键码。怎么做:@keyup.13、@keyup.enter、@keyup.a、@keyup.left、@keyup.space 都可;组合键写 @keyup.ctrl.c。转写中单独按 Ctrl 不触发,按 Ctrl+C 才触发;左箭头可写 .left 或 37,空格可写 .space 或 32。易错点:键码不要死记,优先写键名;组合键要写完整组合,单独修饰键一般不适合直接当触发条件。
html
<input @keyup.enter="search" @keyup.left="prev">
<input @keyup.ctrl.c="copy">JSONP 复习
为什么需要:Ajax 受同源策略限制,协议、域名、端口三者完全一样才不跨域;JSONP 用 script 标签可跨域请求数据。怎么想:JSON 是数据格式,JSONP 是请求方式,两者没有可比性。怎么做:先定义回调函数,再动态创建 script,src 指向接口,接口返回 函数名(数据)。转写中直接写 script 不 append 到 body 不发请求;加载完在 onload 里删除 script。易错点:函数要先定义再调用;script 要插入页面;服务端返回的是可执行 JS 调用,不是纯 JSON;JSONP 只支持 GET。
js
const s = document.createElement('script')
s.src = 'http://127.0.0.1/info2?callback=fn'
document.body.appendChild(s)
s.onload = () => document.body.removeChild(s)JSONP 应用与易错点
为什么需要:百度、京东等第三方接口常用 JSONP;动态回调名可避免函数名冲突。怎么想:随机生成函数名,挂到 window 上,把名字作为 callback 参数传给服务端;服务端用 req.query.callback 替换返回的函数名。怎么做:转写中用 Math.random().toString(36).slice(2) 生成名字,前面拼字母避免数字开头;用完 delete window[fnName]。有些接口固定回调名,不接收自定义 callback,就要按对方要求写。易错点:控制台报 FN is not defined,说明服务端返回了 FN(...),但页面没有这个函数;跨域要比较协议、域名、端口;JSONP 不能发 POST。
本节要解决的问题
这一节只围绕两个指令:v-text 和 v-html。它们都属于“把 Vue 实例里的数据输出到页面上”的指令。为什么需要:插值表达式 {{str}} 能输出数据,但有时我们希望整个标签的内容由某个数据直接决定,而不是在标签内部混写文本和插值;同时还要区分“按纯文本输出”和“按 HTML 解析输出”。怎么想:先准备 data,再让指令的值指向 data 中的属性名,最后观察页面是否被指令结果覆盖。怎么做:看下面完整示例。易错点:指令值不是随便写的展示文本,而是要能对应到实例数据;反引号在 HTML 属性里也不是 JS 模板字符串。
完整示例与数据来源
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/vue.js"></script>
</head>
<body>
<div id="root">
<h3>v-text</h3>
<p v-text="userName">{{str}}</p>
<p v-text='userName'>{{str}}</p>
<p v-text=userName>{{str}}</p>
<p v-text=`userName`>{{str}}</p>
<hr/>
<h3>v-html</h3>
<p v-html="userName">{{str}}</p>
<p v-html='userName'>{{str}}</p>
<p v-html=userName>{{str}}</p>
<p v-html=`userName`>{{str}}</p>
<template v-html="userName">123</template>
</div>
</body>
<script>
// 响应式:视图是通过数据进行驱动的(当数据发生改变,视图会根据所使用的数据的变化而变化)
// 响应式即是视图会响应最新的数据。
// v-text与v-html的共同特点:
// 1- 值是一个字符串
// 2- 该字符串会作为Vue实例的属性名,将对应的属性值在指令所在的标签中进行输出。
// 3- 会覆盖原有数据。
// 4- 属性值可以使用双引号,单引号
// 5- 属性值也可以省略双引号,单引号
// 6- 如果使用的是反引号会将包裹的值在标签中直接输出
// 7- 无法与template结合使用。
// v-text与v-html的区别:
// v-text不支持HTML标签而v-html支持。
new Vue({
el:"#root",
data:{
str:"我是一个字符串",
userName:"zhangsan"
}
})
</script>
</html>这里 data 里有 str 和 userName。v-text="userName" 会把 userName 的值 zhangsan 输出到标签中;标签里原本写的 {{str}} 不会再显示,因为指令会覆盖原有内容。
v-text:把数据写进文本
为什么需要:v-text 用来把数据作为纯文本放进标签,避免在标签内部再写一堆插值。怎么想:它类似设置元素的文本内容,所以即使数据里出现 <b>,也不会被当成标签解析。怎么做:<p v-text="userName"></p>。笔记中单引号、双引号、不加引号三种写法都能指向 userName,页面结果相同。易错点:v-text=`userName` 这种反引号写法,笔记结论是“会将包裹的值在标签中直接输出”,也就是输出字面量 userName,不要误以为它和双引号完全一样。另一个易错点是:<p v-text="userName">{{str}}</p> 最终只显示 zhangsan,原来的 {{str}} 被覆盖,不要以为两者会拼接。
v-html:把数据当 HTML 解析
为什么需要:当数据本身是一段 HTML 字符串,且我们确实希望它被浏览器解析成标签时,用 v-html。怎么想:它和 v-text 的取值方式一样,区别只在“输出时是否解析 HTML”。怎么做:把 userName 改成类似 <b>zhangsan</b> 的字符串,再分别用 v-text 和 v-html 对比,前者显示标签文本,后者显示加粗效果。易错点:v-html 会覆盖标签原有内容;更要注意不要把用户输入直接交给 v-html,否则有 XSS 风险。笔记中的 userName 是普通字符串,所以这里 v-html 看起来和 v-text 结果相近,但本质不同。
覆盖规则与 template 的限制
为什么需要:理解“覆盖”才知道指令和插值谁生效。怎么想:指令作用在所在标签上,会接管该标签的内容。怎么做:保留代码中 {{str}} 做观察,发现被 v-text / v-html 覆盖。易错点:笔记明确写了“无法与 template 结合使用”,代码里 <template v-html="userName">123</template> 不要期待它像普通 p 一样输出;template 是占位容器,不是真实展示标签,指令应写在真实元素上。
从报错到调试:转写稿里的两个提醒
转写稿里讲到 v-model 写成数字 3 会编译失败,原因是它要绑定实例 data 中声明的数据,不能随手写一个值;调试时应先检查“这个名字在 data 里有没有”。这和本节检查 v-text="userName" 是否真能找到 userName 是同一思路。转写稿还提醒写代码要缩进、格式化,遇到问题及时问;这些习惯能减少“属性名拼错、引号写错、指令放错标签”的低级错误。
课堂画面





代码示例
示例一:Vue 实例、挂载与插值表达式
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 实例与挂载</title>
<!-- 1- 引入核心模块,提供了构造函数 Vue -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<p>{{num}}</p>
<p>{{str}}</p>
<p>{{"str"}}</p>
<p>{{1}}</p>
<p>{{bol}}</p>
<p>{{false}}</p>
<p>{{fn}}</p>
<p>{{fn()}}</p>
<p>{{arr}}</p>
<p>{{obj}}</p>
<p>{{null}}</p>
<p>{{undefined}}</p>
<p>{{sex===1?"男":"女"}}</p>
<p>{{str.split("").reverse().join("")}}</p>
</div>
<div id="two">
<h1>{{str}}</h1>
</div>
<script>
// 1- vm 即是 Vue 实例
// 2- Vue 构造函数接收一个配置对象
// 3- data 对象的属性会作为 vm 的实例属性
const vm = new Vue({
el: "#root", // 挂载:指定实例作用的范围
data: {
num: 1,
str: "师法魔级超",
bol: true,
arr: [1, 2, 3, 4],
sex: 1,
obj: { userName: "zhangsan" }
},
methods: {
fn() {
return this.str.split("").reverse().join("");
}
}
});
// data 的属性与实例属性互为镜像(同一个引用)
const data = { a: 1 };
const vm2 = new Vue({ data });
data.a = 100;
vm2.a = 200;
console.log(vm2.a, data.a); // 200 200
// 同一个页面支持多个实例挂载到不同元素
new Vue({
el: "#two",
data: { str: "two" }
});
</script>
</body>
</html>关键行与易错点
el: "#root":el只匹配第一个符合条件的元素,同一实例挂载不到第二个div;el: "body"、el: "html"不允许。- 模板中直接用实例属性或方法,不需要
this:{{str}}、{{fn()}};写{{this.str}}、{{vm.str}}也能出结果,但不建议。 {{undefined}}、{{null}}不输出任何内容。- 数组不会像 React 那样展开,
{{arr}}输出1,2,3,4;对象可以输出(React 会报错)。 {{fn}}输出的是函数体,{{fn()}}才是函数运行结果——注意加括号。data与实例属性双向同步:改data.a或改vm.a都会同步到另一边。methods里的方法不要漏写逗号或缩进混乱(写f2时极易漏逗号导致整个methods不生效),写完随手格式化。
示例二:v-text 与 v-html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>v-text 与 v-html</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h3>v-text</h3>
<p v-text="userName">{{str}}</p>
<p v-text='userName'>{{str}}</p>
<p v-text=userName>{{str}}</p>
<p v-text=`userName`>{{str}}</p>
<hr />
<h3>v-html</h3>
<p v-html="userName">{{str}}</p>
<p v-html='userName'>{{str}}</p>
<p v-html=userName>{{str}}</p>
<p v-html=`userName`>{{str}}</p>
<hr />
<!-- 用带标签的字符串对比两者区别 -->
<p v-text="htmlStr">{{str}}</p>
<p v-html="htmlStr">{{str}}</p>
</div>
<script>
// 响应式:视图由数据驱动,数据改变,视图随之变化
new Vue({
el: "#root",
data: {
str: "我是一个字符串",
userName: "zhangsan",
htmlStr: "<b>我是加粗的内容</b>"
}
});
</script>
</body>
</html>关键行与易错点
v-text/v-html的值是实例上的属性名,不是字符串字面量,所以v-text="userName"拿到的是zhangsan。- 两者都会覆盖标签内原有内容,
{{str}}被清掉,不会同时显示。 - 引号可写双引号、单引号,也可以省略;但用反引号时不会当作表达式求值,会把包裹的内容原样输出。
- 唯一区别:
v-text不解析 HTML 标签,v-html会解析——htmlStr一行能直观看出差别。 - 这两个指令不能和
<template>结合使用(<template v-html="userName">无效)。 v-html会解析外部 HTML,真实项目里不要用它渲染用户输入的内容。
示例三:事件修饰符(stop / prevent / once / 串联)与键盘修饰符
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件修饰符</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
#outer { width: 500px; height: 300px; background: yellow; }
#middle { width: 300px; height: 200px; background: red; }
</style>
</head>
<body>
<div id="root">
<!-- 1- 阻止冒泡 .stop -->
<div id="outer" @click="outerHandler">
<div id="middle" @click="middleHandler">
<button @click.stop="btnHandler">按钮</button>
</div>
</div>
<!-- 2- 串联修饰符:.once 只生效一次,.prevent 阻止默认跳转 -->
<form action="https://www.baidu.com/s">
<input name="wd" placeholder="输入内容" />
<button @click.once.prevent="searchHandler">搜索</button>
</form>
<!-- 3- 键盘修饰符:写键名或 keyCode 都可以 -->
<input
placeholder="试试 回车 / 字母A / 左箭头 / ctrl+c"
@keyup.enter="keyHandler"
@keyup.13="keyHandler"
@keyup.a="keyHandler"
@keyup.65="keyHandler"
@keyup.left="keyHandler"
@keyup.37="keyHandler"
@keyup.ctrl.c="keyHandler" />
</div>
<script>
new Vue({
el: "#root",
methods: {
btnHandler() { alert("按钮"); },
middleHandler() { alert("红色区域"); },
outerHandler() { alert("黄色区域"); },
searchHandler() { alert("搜索"); },
keyHandler(e) { console.log("触发了键盘事件:", e.key); }
}
});
</script>
</body>
</html>关键行与易错点
- 不写
.stop时,点按钮会依次触发「按钮 → 红色区域 → 黄色区域」,这就是冒泡(由里向外扩散);@click.stop等价于手写e.stopPropagation()。 .prevent阻止默认行为,等价于e.preventDefault(),用在表单提交按钮上就不会跳转到action地址。.once让事件只作用一次,第二次点击相当于没绑定事件(此时搜索按钮就会正常提交跳转)。- 串联修饰符:
@click.once.prevent表示两个修饰符同时生效,顺序不影响结果。 - 键盘修饰符可写键名
enter、a、left,也可写编码13、65、37,二者等价。 .ctrl这类系统修饰键单独按下不触发,必须和其他键组合成.ctrl.c才有效;上例中光标在输入框里按ctrl+c才会打印日志。- 以上写法(任意键名别名、数字 keyCode、系统修饰键)都是 Vue 2 的语法,Vue 3 已移除,练习时注意版本。
常见错误
1. el 写了 div / body,页面只渲染一半或直接报错
- 现象:两个
<div>都写了{{str}},只有第一个被渲染,第二个原样显示花括号;写el:"body"时整个页面被替换。 - 原因:
el匹配到多个元素时只取第一个;body/html是根节点,挂载会整体替换其内容。 - 修法:挂载点一律用唯一的
id,一块内容对应一个实例;确实需要多块就用多个new Vue,不要指望一个实例管多个元素。
2. 模板里写 {{this.str}} 或 {{vm.str}}
- 现象:
{{this.str}}侥幸能出值,{{vm.str}}直接报错或空白。 - 原因:模板作用域就是实例本身,属性直接写名字即可;
vm是外部 JS 变量,模板里访问不到(只有你在data里显式声明了同名属性才行)。 - 修法:模板内一律去掉
this,也不要用外部变量名,直接{{str}}。
3. v-model 绑了字面量或 data 里没有的属性
- 现象:
v-model="3"、v-model="深圳"编译失败或输入框改了数据不联动。 - 原因:
v-model的值必须是一个能在实例上找到的属性名表达式,写死的字面量没有可写入的目标。 - 修法:在
data里先声明该字段;如果只是想要一个初始值,用value="深圳",不要用v-model。
4. v-text / v-html 把标签内原有内容整个吃掉
- 现象:
<p v-text="userName">{{str}}</p>只剩userName的值,str消失。 - 原因:这两个指令是整体替换
textContent/innerHTML,不是追加。 - 修法:需要拼接就用插值表达式
{{a}}{{b}},或用计算属性/方法拼好再输出;v-text只用于"整块替换"的场景。
5. 指令值用了反引号
- 现象:
v-text=`userName`页面上直接显示字符串userName。 - 原因:反引号不被识别为属性引号,整个反引号包裹的内容被当成字面量。
- 修法:统一用双引号(或单引号),团队内保持一致;别混用。
6. .stop 加错了层级,冒泡照样发生
- 现象:点了按钮,外层的
alert还是弹;换成e.stopPropagation()也不生效。 - 原因:
.stop加在了外层元素上(外层本来就没有冒泡可阻止);手写e.stopPropagation()时方法没接形参e。 - 修法:把
@click.stop写在触发事件的最内层元素上;若手写,方法签名必须是fn(e)并调用e.stopPropagation()。
7. .once 和 .prevent 串联后,第二次点击就跳走了
- 现象:
@click.once.prevent第一次点击不提交,第二次点击表单直接跳转。 - 原因:
.once让整个监听器只执行一次,第二次点击等于没绑定,.prevent也随之失效。 - 修法:需要"永远阻止默认行为"就别加
.once;要阻止表单提交,更稳的写法是把@submit.prevent加在<form>上,而不是按钮的click。
8. 键盘修饰符监听 Ctrl 单独按下没反应
- 现象:
@keyup.ctrl按 Ctrl 不触发,@keyup.13却正常。 - 原因:修饰键单独按下/抬起时
keyup行为不可靠,浏览器不一定派发;修饰键一般要配合主键组成组合键。 - 修法:用组合键写法
@keyup.ctrl.c,或用keydown+e.ctrlKey && e.key === 'c'自行判断;数字编码和键名二选一,别混着猜。
9. methods 里方法之间漏逗号 / 缩进错乱,后面的方法集体失效
- 现象:新加的方法调用没反应,控制台报语法错误,前面的方法却好的。
- 原因:对象字面量成员之间必须有逗号,漏一个后面全废;代码没格式化导致结构错位不易察觉。
- 修法:每次写完先格式化(Shift+Alt+F),把"格式化后再提交"当习惯;报错先看第一个错误位置,别从最后一个改起。
10. JSONP 把回调名写死、script 用完不删
- 现象:连续点两次按钮,回调被调用多次;和同事的代码撞名互相覆盖;
<script>在 DOM 里越堆越多。 - 原因:回调名硬编码成全局
FN;script注入后没有移除。 - 修法:回调名随机生成(如
前缀 + Math.random().toString(36).slice(2)),挂到window上并把名字作为callback参数传给服务端;在onload里document.body.removeChild(script),用完delete window[cbName]。
11. 用 JSONP 去发 POST,或服务端返回纯 JSON
- 现象:请求发出去了,回调不执行,控制台报语法错误。
- 原因:JSONP 本质是
<script src>,只能 GET;服务端必须返回cbName({...})这种可执行脚本,返回裸 JSON 会被当语句解析而报错。 - 修法:明确 JSONP 只用于 GET 跨域取数;服务端按
callback参数拼回调名(数据)输出,没有该参数时给默认回调名。
练习
练习 1|挂载边界验证 写一个页面放两个 <div>,都用 {{str}}。先用 el:"div" 观察结果并解释;再改成两个实例分别挂 #one、#two,让两处输出不同值。最后试着写 el:"body",记录现象。
练习 2|插值表达式预测表 在模板里依次写 {{num}}、{{null}}、{{undefined}}、{{obj}}、{{arr}}、{{fn}}、{{fn()}}、{{sex===1?"男":"女"}}、{{str.split("").reverse().join("")}}。先写出你认为的输出,再运行核对,把预测错的项记下来并说明原因。
练习 3|修饰符组合对照 做三层嵌套(黄 500×300 → 红 300×200 → 按钮),三层都绑 @click 各弹一个 alert。完成三组对照并记录差异: ① 全部不加修饰符;② 按钮改 @click.stop;③ 按钮改 @click.once.prevent,连点两次并说明第二次的行为为什么变了。
练习 4|键盘修饰符 一个输入框,用编码和键名两种写法分别监听:回车、字母 a、左箭头、Ctrl+C。要求 Ctrl 单独按下时无输出、Ctrl+C 时才有输出,并说明为什么。
练习 5|JSONP 收口 用动态 script 请求一个返回 回调名(数据) 的接口,要求:回调名随机生成、通过 callback 参数传给服务端、onload 后移除 script 并删除全局函数。连点按钮三次,用开发者工具确认 DOM 中 script 数量始终为 0(加载完成后)且回调不重复触发。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 阻止冒泡stop | 07:07 |
| 2 | 串联 | 07:31 |
| 3 | 键盘修饰符 | 09:21 |
| 4 | jsonp复习 | 36:36 |
| 5 | jsonp应用 | 17:14 |
小结
el是"一块地盘",唯一 id 挂载、一个实例管一块,多块就多实例;body/html永远不能当挂载点。- 模板里只有实例属性名,没有
this、没有外部变量;{{}}里能写表达式,但null/undefined静默不输出。 v-text/v-html是覆盖式写入而非追加,凡是要和原有内容共存就得回到插值或计算属性。v-model的值必须是data里真实存在的属性;只想要初始值,用value而不是v-model。- 修饰符是"给事件行为打补丁",
.stop要打在最内层,.once会让同一条链上的.prevent一起只生效一次——链式写法有顺序语义,不能随手堆。 - 修饰键(Ctrl/Alt/Shift)单独按键不可靠,键盘修饰符要落到组合键或改
keydown判断。 - JSONP 不是 AJAX,它是"借
<script>执行一段服务端拼好的 JS",因此只支持 GET,且必须由服务端按callback参数拼回调名。 - JSONP 的三件收尾工作缺一不可:回调名唯一化、用完删
script、用完删全局函数——否则就是全局污染和重复调用。 - 写完就格式化、报错先看第一条,这两条习惯比多记一个 API 更能省时间。