Skip to content

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=&#96;userName&#96; 这种反引号写法,笔记结论是“会将包裹的值在标签中直接输出”,也就是输出字面量 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 是同一思路。转写稿还提醒写代码要缩进、格式化,遇到问题及时问;这些习惯能减少“属性名拼错、引号写错、指令放错标签”的低级错误。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

示例一: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=&#96;userName&#96; 页面上直接显示字符串 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阻止冒泡stop07:07
2串联07:31
3键盘修饰符09:21
4jsonp复习36:36
5jsonp应用17:14

小结 ​

  1. el 是"一块地盘",唯一 id 挂载、一个实例管一块,多块就多实例;body/html 永远不能当挂载点。
  2. 模板里只有实例属性名,没有 this、没有外部变量;{{}} 里能写表达式,但 null/undefined 静默不输出。
  3. v-text/v-html 是覆盖式写入而非追加,凡是要和原有内容共存就得回到插值或计算属性。
  4. v-model 的值必须是 data 里真实存在的属性;只想要初始值,用 value 而不是 v-model。
  5. 修饰符是"给事件行为打补丁",.stop 要打在最内层,.once 会让同一条链上的 .prevent 一起只生效一次——链式写法有顺序语义,不能随手堆。
  6. 修饰键(Ctrl/Alt/Shift)单独按键不可靠,键盘修饰符要落到组合键或改 keydown 判断。
  7. JSONP 不是 AJAX,它是"借 <script> 执行一段服务端拼好的 JS",因此只支持 GET,且必须由服务端按 callback 参数拼回调名。
  8. JSONP 的三件收尾工作缺一不可:回调名唯一化、用完删 script、用完删全局函数——否则就是全局污染和重复调用。
  9. 写完就格式化、报错先看第一条,这两条习惯比多记一个 API 更能省时间。