知识点
插值语法 && 指令语法
v-bind:XXX 或者 :XXX
案例:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>插值语法</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>hello, {{ name }}</h1>
<hr/>
<a v-bind:href="url_baidu" target="_blank">{{ url_baidu_name }}</a>
<hr/>
<p>学校: <a :href="school.url.toString()"> {{ school.name }} </a> </p>
</div>
<script>
new Vue({
el: '#root',
data: {
name: 'Jungle',
url_baidu: 'https://www.baidu.com',
url_baidu_name: '点击跳转->百度',
school: {
name: '清华大学',
url: 'https://www.tsinghua.edu.cn/'
}
}
})
</script>
</body>
</html>

数据绑定
Vue中有2种数据绑定的方式:
1.单向绑定(v-bind):数据只能从data流向页面。
2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
备注:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue 数据绑定</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<label>
单项数据绑定:
<!--<input type='text' v-bind:value="name"/>-->
<!--简写-->
<input type='text' :value="name"/>
</label>
<label>
双向数据绑定:
<!--<input type='text' v-model:value="name"/>-->
<input type='text' v-model="name"/>
</label>
<br/>
<!--
不是什么都可用v-model的 这里v-model不支持h1
v-model只能应用在表单元素上(输入元素),与用户交互(都有共同的value属性)
-->
<h1 v-bind:x="name">
你好啊
</h1>
</div>
<script type="text/javascript">
//v-bind可以完成数据绑定(单项绑定)
//v-model双向数据绑定
//单项数据绑定 双向数据绑定
Vue.config.productionTip = false;
new Vue({
el: '#root',
data:{
name: 'shanghai'
}
})
</script>
</body>
</html>

el和data的两种写法
data与el的2种写法
1.el有2种写法
(1).new Vue时候配置el属性。
(2).先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值。
2.data有2种写法
(1).对象式
(2).函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
3.一个重要的原则:
由Vue管理的函数(例如data),一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
el的第一种写法
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>el和data的两种写法</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>你好,{{ name }}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const v = new Vue({
el: '#root', //第一种写法
data: {
name: '上海'
}
});
</script>
</body>
</html>
el的第二种写法
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>el和data的两种写法</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>你好,{{ name }}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data: {
name: '上海'
}
});
// 关联root容器,用mount方法
setTimeout(() => {
vm.$mount('#root'); //第二种写法 挂载到页面上
}, 3000);
</script>
</body>
</html>
data的第一种写法:对象式
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>el和data的两种写法</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>你好,{{ name }}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#root',
//data的第一种写法:对象式
data: {
name: 'shanghai'
}
});
</script>
</body>
</html>
第二种写法: 函数式(返回对象中包含你想渲染的模版数据)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>el和data的两种写法</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>你好,{{ name }}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#root',
//第二种写法: 函数式(返回对象中包含你想渲染的模版数据)
//学习组件的时候要是用函数式 这个函数是Vue来调用的
// data: () => { //箭头函数
// console.log(`@@@@`, this); //此时this是window,因为箭头函数没有自己的this,它向外查找找到的window
// return ({
// name: 'shanghai'
// })
// }
//尽量不要写成剪头函数,因为会丢失this
data () { //普通函数
console.log('@@@', this); //此时this是Vue
return {
name: 'shanghai'
}
}
});
</script>
</body>
</html>
Vue 中的使用原则
一个简单的判断原则:
- 由 Vue 管理的函数:例如
data(),methods中的方法,computed计算属性,生命周期钩子(如mounted)等。这些函数需要被 Vue 调用,并且需要访问 Vue 实例 (this),所以必须使用普通函数。- 不由 Vue 管理的函数:例如
setTimeout的回调函数,或者一些需要保持this指向外部 Vue 实例的场景。为了避免this指向丢失,可以使用箭头函数来“继承”外层的this。总结:在 Vue 2 的选项式 API 中,除了特定的回调场景,绝大多数情况下都应该使用普通函数。
=> 函数不是在new vue里面定义的嘛? 为什么还会指向window?
简单直接的回答是:虽然函数是在 new Vue({}) 的配置对象里“定义”的,但决定 this 指向的不是“定义”的位置,而是“执行”的位置。
在箭头函数的特殊规则下,它向外找的“父级作用域”确实是全局的 window。
我们来一步步拆解为什么会出现这种情况:
1. 区分“定义”和“执行”
请看下面这段代码,思考 this 指向谁:
const obj = {
name: '我是obj',
// 这里只是把函数定义好,存进去了,并没有运行
sayHello: function() {
console.log(this.name);
}
}
// 这里才是函数真正被执行的时候
// 谁调用的?是 obj.sayHello() 调用的,所以 this 指向 obj。
obj.sayHello();
回到你的 Vue 代码:
- 定义时:你在
new Vue({...})的大括号里写了箭头函数。此时,这段代码处于全局作用域(或者模块作用域)中。 - 执行时:Vue 底层拿到你传入的对象后,会去执行这个
data函数。
对于普通函数,Vue 可以通过 .call() 或 .apply() 强行指定它的 this 是 Vue 实例。
对于箭头函数,它没有自己的 this,它会忽略 Vue 的调用方式,直接去“捕获”它定义时所在环境的 this。
2. 箭头函数的“词法绑定”规则
箭头函数的 this 遵循词法作用域。它不关心是谁调用的,只关心它写在哪儿。
在你的 HTML 文件中,<script> 标签里的代码属于全局作用域。
// 这里的环境就是全局环境
// this 指向 window (非严格模式下)
console.log('脚本顶层的this:', this);
new Vue({
data: () => {
// 箭头函数没有自己的 this
// 它向外层(即 <script> 标签的顶层)查找
// 所以这里的 this 就是上面打印出的 window
console.log('箭头函数里的this:', this);
return {}
}
})
3. 为什么普通函数就没问题?
普通函数(function)的 this 是动态绑定的,取决于“谁调用了我”。
Vue 源码内部大致是这样处理的:
// 伪代码
const vm = new Vue(options);
// Vue 拿到你传入的 data 函数,并用 call 方法调用它
// 这里的 this 就被强行指向了 vm(Vue 实例)
options.data.call(vm);
因为普通函数的 this 是由调用者(vm)决定的,所以能正确指向 Vue 实例。而箭头函数无法被 call 或 bind 改变 this 指向,它死死地绑定了定义时的 window。
总结
虽然看起来箭头函数是写在 new Vue({...}}) 里面的,但在 JavaScript 引擎看来:
- 定义位置:它处于全局
<script>环境中。 - 父级作用域:它的父级就是全局作用域,也就是
window。 - 结果:箭头函数会把
this永久地绑定为window,无论 Vue 怎么调用它,都无法改变。
箭头函数无法被 call 或 bind 改变 this 指向是因为他是匿名函数?
不是的,这两者没有直接关系。“是否是匿名函数”与“this指向能否被改变”无关。
箭头函数无法被 call 或 bind 改变 this 指向,根本原因在于它的设计机制:**箭头函数没有自己的 this**。
为了让你彻底理解,我来拆解一下这两者的逻辑:
1. 核心原因:箭头函数没有“this”
普通函数(无论具名还是匿名)在被调用时,JavaScript 引擎会为它创建一个执行上下文,并绑定一个 this。这个 this 可以被 call、apply 或 bind 强行修改。
而箭头函数不同,它**根本不去创建自己的 this**。它在定义时,就通过“词法作用域”直接“继承”了外层(父级)作用域的 this。
既然它内部没有属于自己的 this,call 和 bind 就没有东西可以去“修改”。你传入的参数(thisArg)会被它直接忽略。
2. 为什么说和“匿名”无关?
匿名函数只是指“没有名字的函数”。普通函数也可以是匿名的,而匿名的普通函数完全可以被 call 改变 this。
反例证明:
下面这段代码是一个匿名的普通函数,你看它是可以被 call 改变 this 的:
// 这是一个匿名函数(没有名字),也是一个普通function
const normalAnon = function() {
console.log(this.name);
};
// 虽然是匿名的,但是 call 完全生效!
normalAnon.call({name: '我是Call改变的'}); // 输出: 我是Call改变的
结论:
只要是普通函数(哪怕没有名字),它就有 this,就能被改变。
只要是箭头函数(哪怕你给它赋值给一个变量),它就没有 this,就不能被改变。
3. 一个简单的类比
你可以把普通函数想象成“租房客”,call 就像是中介,可以强行把租房客塞进不同的房子里(改变 this)。
而箭头函数就像是“跟屁虫”,它没有自己的家,它永远紧紧跟着它爸爸(外层作用域)。无论中介(call)怎么折腾它,它都死死抓着它爸爸不放,根本不住进你给它的新房子里。
MVVM模型
MVVM模型
1. M:模型(Model) :data中的数据
2. V:视图(View) :模板代码
3. VM:视图模型(ViewModel):Vue实例
观察发现:
1.data中所有的属性,最后都出现在了vm身上。
2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>mvvm模型的理解</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!--view-->
<h1>学校名称: {{ name }} </h1> <hr/>
<h1>学校地址:{{ address }} </h1> <hr/>
<h1>测试一下1: {{ 1 + 1 }} </h1> <hr/>
<!--所有viewModel和vue原型上的属性在视图中的{{}}都可以看到-->
测试一下2: {{ $options }} </h1> <hr/>
<h1>测试一下3: {{ $emit }} </h1> <hr/>
<h1>测试一下4: {{ _c }} </h1> <hr/>
</div>
<script type="text/javascript">
//view model view与model之间的纽带
//vm view实例对象
const vm = new Vue({
el: '#root',
data(){
//model
return {
name:'武汉科技大学',
address: '武汉'
}
}
});
console.log(vm);
</script>
</body>
</html>
运行效果:

MVVM 其实就是一种让网页开发更高效的“分工协作”模式。结合你提供的代码来看,它把页面拆解为三个明确的角色,让数据和界面各司其职,又能自动同步。
我们可以用你代码中的例子来具体拆解这三个部分:
🧱 M - 模型
是什么:对应你代码中的
data函数返回的对象。职责:它就是纯粹的数据,不包含任何 HTML 标签或样式。
例子:
data(){ return { name: '武汉科技大学', // 学校名字数据 address: '武汉' // 学校地址数据 } }这就是模型。它只负责存储和管理数据。
🎨 V - 视图
是什么:对应你代码中的
<div id="root">里的 HTML 模板。职责:它是用户看到的界面。它负责决定数据长什么样。
例子:
<h1>学校名称: {{ name }}</h1> <h1>学校地址: {{ address }}</h1>这就是视图。它只负责展示,它通过
{{ }}告诉系统:“我要在这里显示
name和
address的值”。
🔄 VM - 视图模型
是什么:对应你代码中的
new Vue({})实例(也就是vm对象)。职责:它是连接
M和V的纽带(桥梁)。它的核心任务是监听数据变化,并自动更新界面;或者监听用户操作(如点击、输入),并自动更新数据。例子:
const vm = new Vue({ el: '#root', // 指定控制哪个区域 data(){ ... } // 提供数据 });Vue 实例(VM)内部使用了数据劫持(Object.defineProperty 或 Proxy)和发布订阅模式。它会默默地盯着 data 里的数据(M),一旦数据变了(比如 data 改了),它就立刻通过 el 找到视图(V),把页面上对应的 刷新成新的值。
🚀 核心优势:双向绑定
MVVM 最大的魅力在于“自动同步”。
在传统的开发(如原生 JS)中,你需要手动操作 DOM:
- 数据变了 -> 2. 找到 DOM 元素 -> 3. 修改 innerHTML。
而在 MVVM 中,这一切都被 VM 搞定了:
- 数据(M)变了 -> 2. VM 监听到 -> 3. 自动更新视图(V)。
你只需要关心数据(M),视图(V)会自动跟着变。这就是所谓的数据驱动视图。
🔍 补充:你代码中注释的那两点观察
你的代码注释中提到了两个非常关键的细节,这正是 Vue 实现 MVVM 的具体表现:
data中所有的属性,最后都出现在了 vm 身上: Vue 把data里的数据代理到了 Vue 实例(vm)上。所以你既可以用vm.$data.name访问,也可以直接用vm.name访问。- vm 身上所有的属性及 Vue 原型上所有属性,在 Vue 模板中都可以直接使用: 这就是为什么你在
{{ }}里可以直接写1+1,或者直接调用 Vue 实例上的方法(如$emit)。因为模板里的代码是在 Vue 实例的上下文中执行的,this指向的就是 VM。
📌 总结
- M:就是数据(
data)。 - V:就是页面(HTML)。
- VM:就是 Vue 实例(
new Vue()),它让 M 和 V 保持同步,开发者无需手动操作 DOM。
数据代理-Object.defineProperty方法
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Object.defineProperty</title>
</head>
<body>
<script type="text/javascript">
let number = 18;
let person = {
name: '张三',
sex:'男'
}
//此时age是不可以枚举的
//往person对象里添加属性
//关联
Object.defineProperty(person,'age', {
// value: 18,
// enumerable: true, //此时代表这个属性是可以枚举的
// writable: true, //代表可以重写该属性(控制属性是否被修改)
// configurable:true, //控制属性是否可以被删除 默认为false
//当读取person的age属性时get属性就会被调用,且返回值就是age的值
//invoke property proxy映射数据代理
get: function () {
//测试它的调用情况
console.log('@@@ GET AGE');
//此时age的值依赖number的值
return number
},
//当修改person的age属性时set(setter)属性就会被调用,且会收到修改的具体值
set(v) {
//测试
console.log('CHANGE AGE');
number=v;
}
});
// console.log(Object.keys(person))
//遍历
// for(var p in person){
// console.log('@@', person[p])
// }
console.log(person);
</script>
</body>
</html>
Object.defineProperty() 是 JavaScript 中一个非常底层且强大的方法,你可以把它理解为给对象属性装上了一个“智能监控探头”(或者叫“拦截器”)。
在你的代码中,它不仅仅是在给 person 对象添加一个 age 属性,而是在定义一种关系:当你读这个属性时,执行什么逻辑;当你写这个属性时,执行什么逻辑。
结合你的代码,我来为你拆解这个概念:
1. 核心概念:它不只是赋值
通常我们给对象加属性是这样做的:
person.age = 18; // 简单粗暴,存进去一个死数据
这种方式存进去的只是一个静态值。
而 Object.defineProperty 是这样做的:
Object.defineProperty(person, 'age', { /* 描述符 */ });
它允许你通过描述符(Descriptor)对象,精确地控制这个属性的行为。它就像是给 age 属性立了一份“合同”,规定了读写时的规则。
2. 你的代码解析:数据劫持与代理
你的代码实现了一个非常经典的数据代理(Data Proxy)效果。我们来看这份“合同”是怎么签的:
属性的“读取”监控 (get)
get: function () {
console.log('@@@ GET AGE'); // 只要读age,这句话就会打印
return number; // 实际返回的是外部变量 number 的值
}
- 含义:你告诉 JavaScript:“每当有人问
person.age是多少时,请不要直接拿内存里的值,而是先执行这个get函数,并把它的返回值当作age的值。” - 效果:
person.age的值不再是固定的,而是动态计算出来的(它依赖于number变量)。
属性的“修改”监控 (set)
set(v) {
console.log('CHANGE AGE'); // 只要改age,这句话就会打印
number = v; // 把新值 v 存到外部变量 number 里
}
- 含义:你告诉 JavaScript:“每当有人试图修改
person.age时,请执行这个set函数,并把用户赋的新值传进来。” - 效果:修改
person.age并不会直接改变person内部的某个私有字段,而是改变了外部的number变量。
3. 为什么遍历不到 age?
你在代码中写了:
console.log(Object.keys(person)) // 结果是 ['name', 'sex']
你会发现 age 不在其中。这是 Object.defineProperty 的另一个特性:默认配置。
如果不显式设置,通过 defineProperty 定义的属性默认是不可枚举的(enumerable: false)。
- 不可枚举意味着:它不会出现在
for...in循环、Object.keys()、JSON.stringify()的结果中。 - 潜台词:这个属性是“隐藏”的,虽然你可以通过
person.age访问它,但程序遍历对象时会假装看不见它。
4. 形象的比喻
你可以把 Object.defineProperty 想象成房产中介:
- 普通属性:就像你直接把房子钥匙给了别人(直接访问内存地址)。
- defineProperty 属性:你把房子钥匙锁在自己手里,告诉中介(
get/set):“有人要看房(get),你带他去;有人要改装修(set),你告诉我”。
在你的代码里:
number是真正的房子(数据源)。person.age是一个挂牌(虚拟属性)。get/set是中介。- 当你问
person.age时,中介跑去number那里拿数据给你。 - 当你改
person.age时,中介把新数据存回number。
5. 它的实战意义(Vue 2 的核心)
你在学习 Vue,这个概念至关重要。Vue 2 的响应式原理就是基于 Object.defineProperty 实现的。
- 依赖收集:当模板渲染读取
person.age时,get被触发,Vue 知道“哦,这个视图依赖了 age”。 - 派发更新:当数据改变(
set被触发)时,Vue 知道“age 变了”,于是通知所有依赖它的视图去重新渲染。
数据代理-何为数据代理?
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>定义数据代理</title>
</head>
<body>
<!-- 数据代理,通过一个对象代理另一个对象中属性的操作 -->
<script type="text/javascript">
let obj = { x:200 }
let obj2 = { y:200 }
//通过obj2代理obj,来操作obj的x
Object.defineProperty(obj2, 'x',{
get(){
return obj.x
},
set(v){
obj.x = v;
}
});
</script>
</body>
</html>
数据代理就是:找一个“替身”(代理对象),让你通过这个替身去操作另一个对象(目标对象)的属性。
在你的代码中,obj2 就是那个“替身”(代理),obj 是真正的“本人”(目标)。你表面上是在操作 obj2.x,实际上是在读写 obj.x。
🧩 代码拆解:它是如何工作的?
让我们看看你的代码是如何实现这个“替身”机制的:
let obj = { x: 200 } // 目标对象(真正的数据持有者)
let obj2 = { y: 200 } // 代理对象(替身)
这里我们有两个独立的对象。现在,我们通过 Object.defineProperty 给 obj2 装上一个“监控器”:
**定义读取行为 (
get)**:get(){ return obj.x // 当你问 obj2.x 时,它实际上去问 obj.x }- 效果:当你访问
obj2.x时,JavaScript 会执行这里的get函数,它会去obj那里把x的值拿回来。 - 比喻:你问替身“你的年龄是多少?”,替身转身去问本人,然后把本人的答案告诉你。
- 效果:当你访问
**定义修改行为 (
set)**:set(v){ obj.x = v; // 当你修改 obj2.x 时,它实际上修改的是 obj.x }- 效果:当你给
obj2.x赋值(比如obj2.x = 300)时,JavaScript 会执行这里的set函数,它会把新值存入obj.x。 - 比喻:你告诉替身“把你的年龄改成30”,替身转身去把本人的年龄改成了30。
- 效果:当你给
🎬 生活中的比喻
想象一下“明星”和“经纪人”的关系:
- **目标对象 (
obj)**:大牌明星(真正的数据源)。 - **代理对象 (
obj2)**:明星的经纪人(代理)。 - 你:应用程序。
当你想知道明星的行程(数据)时,你不直接联系明星(obj),而是联系经纪人(obj2)。
- 读取:你问经纪人:“明星明天在哪?” 经纪人(
get)去查明星的日程表,然后告诉你。 - 修改:你告诉经纪人:“把明星明天的行程改到上海。” 经纪人(
set)去修改明星的日程表。
你操作的一直是经纪人,但你影响的却是明星。 这就是数据代理。
💡 为什么要用数据代理?(Vue 的视角)
在 Vue 2 中,这个机制被用来实现响应式系统:
- 数据劫持:Vue 把你的
data对象(obj)里的所有属性,通过Object.defineProperty代理到了 Vue 实例(vm/obj2)上。 - 监听变化:因为有了
get和set,Vue 就能在你读数据时(get)知道“哦,这个组件依赖了这个数据”,在你改数据时(set)知道“哦,数据变了,我要去更新页面”。
vue中的数据代理
1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处:
更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue中的数据代理</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址: {{ address }}</h2>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
let data;
//通过vm代理
const vm = new Vue({
el:'#root',
//vm._data === options.data = data
//
data(){
return data = {
name:'panyue',
address:'shanghai'
}
}
});
</script>
</body>
</html>
Vue 2 的数据代理机制确实就是这么运作的。
结合你的代码,我们可以把这个过程看作是 Vue 在 vm(Vue 实例)和 data(数据源)之间搭建了一座“隐形桥梁”。下面我来配合代码,一步步拆解你列出的这三个点:
🎭 什么是数据代理?
定义:通过 vm 对象来代理对 data 对象中属性的操作(读/写)。
在你的代码中:
- 目标对象(真正的数据):
data变量(值为{ name: 'panyue', address: 'shanghai' })。 - 代理对象(替身):
vm(Vue 实例)。
现象:
- 你模板中写的是
{{ name }}(看起来像是在找vm要name)。 - 你脚本中写的是
vm.name = 'new'。 - 但实际上,
name属性原本并不属于vm,它属于data。vm只是充当了“中间人”。
🌉 基本原理:Object.defineProperty 的魔法
Vue 内部在初始化时,大致做了这么一件事(模拟源码逻辑):
// 1. 拿到你的 data
let data = { name: 'panyue', address: 'shanghai' };
// 2. 遍历 data 的所有属性
Object.keys(data).forEach(key => {
// 3. 把属性添加到 vm 上,并定义 get/set
Object.defineProperty(vm, key, {
get() {
// 读取 vm.name 时,实际读取 data.name
return data[key];
},
set(newValue) {
// 修改 vm.name 时,实际修改 data.name
data[key] = newValue;
}
});
});
发生了什么?
- 当你访问
vm.name时,触发了get,它去data里取值。 - 当你修改
vm.name = 'hello'时,触发了set,它去data里改值。
🤔 为什么要这么做?(好处)
你提到“方便操作数据”,这不仅仅是少打几个字,而是有更深层的设计考量:
- 模板中无需前缀:
- 如果没有代理,你在模板里可能得写
{{ data.name }}。 - 有了代理,直接写
{{ name }},代码更简洁,语义更清晰。
- 如果没有代理,你在模板里可能得写
- 统一上下文:
- 在 Vue 的
methods、computed中,this指向vm。 - 因为有了代理,你直接用
this.name就能拿到数据,不需要去查文档或者记混该用this.$data.name还是this.name。
- 在 Vue 的
- 实现响应式的基础(最关键!):
- 代理层(
get/set)是 Vue 的“监控室”。 - 依赖收集:在
get里,Vue 知道“哦,这个视图用到了这个数据”。 - 派发更新:在
set里,Vue 知道“哦,数据变了,我要去通知视图更新”。
- 代理层(
🔍 验证一下
你可以试着在你的代码后面加上这几行,看看代理的“痕迹”:
// 1. 证明 vm 代理了 data
console.log(vm.name); // 'panyue' (直接通过 vm 读取)
// 2. 修改 vm 的属性,实际上修改了 data
vm.name = 'changed';
console.log(data.name); // 'changed' (data 被同步修改了)
// 3. 查看 vm 的属性描述符
// 你会发现 vm.name 是一个访问器属性 (有 get/set),而不是一个数据属性
console.log(Object.getOwnPropertyDescriptor(vm, 'name'));
总结:
Vue 通过 Object.defineProperty 把 data 的属性“映射”到了 vm 上,让你感觉数据就在 vm 上一样。这既方便了开发者,又让 Vue 有机会在底层监听所有的数据变动,从而实现视图的自动更新。
事件处理-键盘事件
1.Vue中常用的按键别名:
回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab (特殊,必须配合keydown去使用)
上 => up
下 => down
左 => left
右 => right
2.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
3.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2).配合keydown使用:正常触发事件。
4.也可以使用keyCode去指定具体的按键(不推荐)
5.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue 键盘事件</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!--vue中键盘事件的绑定 一般用keyUp(keydown)-->
<h1>欢迎来到{{ name }}</h1>
<input type="text" @keyup="showKeyInfo" placeholder="按键映射---按下回车提示输入"/>
<input type="text" @keyup.enter="showInfo" placeholder="按下回车提示输入"/>
<input type="text" @keyup.delete="showInfo" placeholder="退格或删除提示输入"/>
<input type="text" @keyup.esc="showInfo" placeholder="按下esc提示输入"/>
<input type="text" @keydown.tab="showInfo" placeholder="按下tab示输入"/>
<input type="text" @keyup.ctrl="showInfo" placeholder="[ctrl]按下command提示输入"/>
<input type="text" @keydown.p="showInfo" placeholder="[ctrl]按下p提示输入"/>
<!--键盘事件的修饰符也可以连用-->
<input type="text" @keydown.command.g="showInfo" placeholder="[ctrl]按下command和g提示输入"/>
</div>
<script type="text/javascript">
new Vue({
el:'#root',
data:{
name: 'shanghai'
},
methods:{
showKeyInfo:function (e){
console.log("按键名: ", e.key, "按键码: ", e.keyCode);
console.log(e.target.value);
},
showInfo:function (e){
// if(e.keyCode === 13) {
// console.log(e.target.value);
// }
console.log(e.target.value);
console.log(e.key); //按下的名字
}
}
})
</script>
</body>
</html>
这段代码完美地演示了 Vue 2 中如何优雅地处理键盘事件。Vue 对原生的 keydown 和 keyup 事件进行了增强,提供了按键别名和修饰符,让我们不再需要死记硬背 keyCode。
结合你的代码和注释,我来为你梳理一下 Vue 键盘事件的核心要点:
🧱 基础用法
绑定方式:@keyup 或 @keydown
- **
@keyup**:释放按键时触发。 - **
@keydown**:按下按键时触发(不放会一直触发)。
🏷️ 常用按键别名
Vue 为常用的按键提供了别名,直接通过 . 调用,无需记忆 keyCode。
| 别名 | 对应按键 | 备注 |
|---|---|---|
.enter |
回车键 | 常用于表单提交 |
.delete |
删除/退格键 | 捕获 Delete 和 Backspace |
.esc |
退出键 | 常用于关闭弹窗 |
.space |
空格键 | |
.tab |
制表键 | 特殊:一般配合 keydown 使用,因为无法监听“释放 tab” |
.up / .down / .left / .right |
方向键 |
⚙️ 系统修饰键 (特殊用法)
修饰键包括 .ctrl、.alt、.shift、.meta (Mac 上的 command 键)。
它们的触发逻辑取决于你配合的是 keydown 还是 keyup:
- 配合
@keydown:按下修饰键时,事件正常触发。 - 配合
@keyup:必须先按住修饰键,再按其他键,最后释放其他键,事件才会触发。
代码示例解析:
<!-- 按下 command 键时,事件就会触发 -->
<input type="text" @keydown.meta="showInfo" />
<!-- 必须先按住 command,再按其他键,最后松开其他键才会触发 -->
<input type="text" @keyup.meta="showInfo" />
✨ 自定义别名与连用
连用修饰符:
你可以将多个修饰符连在一起使用,表示“组合键”。<!-- 按下 command + g 才触发 --> <input type="text" @keydown.command.g="showInfo" />自定义按键别名:
如果 Vue 没有提供你需要的别名(比如 F1 键),你可以通过全局配置添加:// 定义一个叫 'f1' 的别名,对应 keyCode 112 Vue.config.keyCodes.f1 = 112;然后就可以这样使用:
<input type="text" @keyup.f1="showInfo" />
💡 关于 tab 键的特别说明
在你的代码中,tab 键必须配合 keydown 使用:
<input type="text" @keydown.tab="showInfo" />
原因:tab 键的主要功能是切换焦点。如果你使用 @keyup.tab,当用户按下 tab 并释放时,焦点已经转移到下一个元素了,此时 keyup 事件往往无法被正确监听到。
📌 总结
Vue 的键盘事件修饰符极大地简化了开发体验:
- 别名:告别记忆
keyCode。 - 修饰符:轻松实现组合键判断。
- 自定义:灵活扩展。
注意:在 Vue 3 中,.keyCode 的使用已被移除,推荐直接使用按键名称(kebab-case 格式,如 .page-down)。
事件处理-Command 键
在 Vue 的键盘事件修饰符中,command 实际上是 .meta 修饰符的别名(在 Mac 系统下)。
为了让你彻底理解,我们需要把它拆解为 “概念名称” 和 “具体按键” 两个层面来看:
1. 核心概念:.meta
在 Vue 的官方文档和源码中,这个修饰符正式的名字叫 **.meta。
它的设计初衷是为了匹配操作系统键盘上的“元键” (Meta Key)**。
2. 具体指代:看你的电脑系统
这个 .meta 键在不同操作系统对应的物理按键是不同的:
| 系统 | 对应的物理按键 | 符号 | 备注 |
|---|---|---|---|
| Mac OS | Command 键 (Cmd) | ⌘ | 在 Mac 下,.meta 就是 command 键 |
| Windows | Windows 徽标键 (Win) | ⊞ | 在 Windows 下,.meta 就是开始键 |
3. 为什么会有 “command” 这种写法?
虽然 Vue 官方定义的修饰符是 .meta,但在实际开发中(特别是 Mac 用户居多的开发环境),大家习惯称之为 command。
在你的代码中,Vue 为了方便开发者,允许你直接使用按键的名称作为修饰符。
- 因为 Mac 键盘上那个键印的是 “command”,
- 所以 Vue 支持你直接写
.command,它会自动识别这等同于.meta。
💡 总结与建议
在你的代码中:
<!-- 这两种写法在 Mac 上是等价的 -->
<input @keydown.meta="doSomething">
<input @keydown.command="doSomething">
- **
.meta**:是标准的、跨平台的写法(推荐,因为它更通用)。 - **
.command**:是语义化的写法,特指 Mac 上的 command 键(可读性更好,但仅限于你知道目标用户是 Mac 的情况)。
事件处理的基本使用
事件的基本使用:
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
2.事件的回调需要配置在methods对象中,最终会在vm上;
3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
5.@click=”demo” 和 @click=”demo($event)” 效果一致,但后者可以传参;
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue 键盘事件</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!--vue中键盘事件的绑定 一般用keyUp(keydown)-->
<h1>欢迎来到 {{ name }} </h1>
<!-- 按下 command 键时,事件就会触发 -->
<input type="text" @keydown.meta="showInfo" placeholder="按下command键提示输入"/>
<!-- 必须先按住 command,再按其他键,最后松开其他键才会触发 -->
<input type="text" @keyup.meta="showInfo" placeholder="松开command键提示输入"/>
<hr/>
<!--指令语法 v开头 例如v-on:click点击事件-->
<button v-on:click="ClickInfo"> 点击提示输入1 </button>
<!--简写形式 @click-->
<button @click="showClickInfo($event, 'hello vue')"> 点击提示输入2 </button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm= new Vue({
el:'#root',
data:{
name: '江西-南昌'
},
methods:{
showInfo:function (e){
console.log(e.target.value);
console.log(e.key); //按下的名字
},
ClickInfo(e) {
console.log('点击提示输入1');
console.log(e.target);
console.log(this === vm ? 'this 指向 vm' : 'this 指向 事件触发的元素');
},
showClickInfo(e, msg) {
console.log('点击提示输入2');
console.log(e.target);
console.log('接收的参数是: ' + msg);
}
}
})
</script>
</body>
</html>
这段代码非常棒,它同时涵盖了 Vue 中键盘事件的特殊机制和鼠标事件的基础用法。通过它,我们可以清晰地对比 Vue 如何处理不同类型的用户交互。
结合你的代码,我来为你详细解读其中的关键点:
⌨️ 键盘事件:.meta 修饰符的奥秘
你代码中的输入框完美展示了 .meta(Command/Win 键)在 keydown 和 keyup 下的不同表现:
@keydown.meta="showInfo"
- 触发逻辑:只要按下 Command 键,事件就会立即触发。
- 场景:适用于需要“长按”执行操作的功能(比如连续加速)。
@keyup.meta="showInfo"
- 触发逻辑:必须先按住 Command 键,再按另一个键(如 A),然后释放那个另一个键,事件才会触发。
- 注意:如果你只按 Command 键然后松开,是不会触发的。
- 场景:这种写法比较少用,通常我们更习惯用
keydown来监听组合键。
🖱️ 鼠标事件:参数与 this
你定义的两个按钮展示了 Vue 事件处理的两个核心细节:
1. 访问原生事件对象
在原生 JS 中,获取事件对象可能需要处理兼容性问题。但在 Vue 中,Vue 会将原生 DOM 事件作为第一个参数传入回调函数。
- 代码中:
ClickInfo(e)和showClickInfo(e, msg)的第一个参数e就是原生事件对象。 - 用法:你可以通过
e.target获取触发事件的 DOM 元素。
2. 正确的 this 指向
在 methods 中定义的方法,Vue 会自动将 this 绑定为 Vue 实例(vm)。
- 代码中:
console.log(this === vm ? ...)会输出this 指向 vm。 - 重要性:这意味着你可以在事件处理函数中直接通过
this.name访问数据,或者调用this上的其他方法。
💡 一个潜在的优化点
在你的 showClickInfo 方法中,你传入了字符串 'hello vue'。在 Vue 模板中,如果你不加引号,Vue 会把它当成变量名去 data 里找;加了引号,它才会被当作普通的字符串参数。
你的写法是完全正确的:
@click="showClickInfo($event, 'hello vue')"
<!-- 注意 'hello vue' 的单引号 -->
📌 总结
这段代码展示了 Vue 事件处理的三大支柱:
- 指令:
v-on:或@绑定监听。 - 修饰符:
.meta、.enter等让按键监听更语义化。 - 方法:在
methods中集中管理逻辑,享受自动绑定的this和便捷的参数传递。
事件修饰
Vue中的事件修饰符:
1.prevent:阻止默认事件(常用);
2.stop:阻止事件冒泡(常用);
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式;
5.self:只有event.target是当前操作的元素时才触发事件;
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue 键盘事件</title>
<script src="../js/vue.js"></script>
<style>
.demo1{
height: 100px;
background: deepskyblue;
padding: 20px;
}
.box1{
height: 100px;
background: pink;
padding: 20px;
}
.box2{
height: 50px;
background: lightgreen;
padding: 20px;
}
.btn3{
height: 30px;
background: rgb(185, 230, 173);
padding: 5px;
border-radius: 5px;
}
.list{
height: 200px;
width: 200px;
background: salmon;
overflow: auto;
}
.list li {
height: 100px;
}
</style>
</head>
<body>
<div id="root">
<!--vue中键盘事件的绑定 一般用keyUp(keydown)-->
<h1>欢迎来到 {{ name }} </h1>
<br/><br/>
<a href="https://www.baidu.com" @click.prevent="showInfo"> 阻止默认事件(常用) </a>
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo"> 阻止事件冒泡(常用) </button>
</div>
<br/><br/>
<button @click.once="showInfo"> 点击只能触发一次 </button>
<div class="box1" @click.capture="showMsg('box1')">
box1 - capture事件的捕获模式, 让事件以捕获的方式来处理(先捕获再冒泡)
<div class="box2" @click="showMsg('box2')">
box2 - 点击事件默认是冒泡模式, 点击box2时, 先触发box2的点击事件, 再触发box1的点击事件
<br/>
<button class="btn3" @click="showMsg('btn3')"> btn3 </button>
</div>
</div>
<br/><br/>
<button v-on:click="ClickInfo"> 点击提示输入1 </button>
<button @click="showClickInfo($event, 'hello vue')"> 点击提示输入2 </button>
<br/><br/>
<div class="demo1" @click.self="showInfo">
<p>self 只有 event.target 是当前操作的元素时, 才会触发事件 (变相阻止冒泡)</p>
<button @click="showInfo"> click.self </button>
</div>
<br/><br/>
<p>passive: 事件的默认行为立即执行, 无需等待事件回调执行完毕.</p>
<p>scroll: 滚动条一滚动就会触发的事件 wheel 鼠标滚轮事件</p>
<ul class="list" @scroll.passive="demo">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
<br/><br/>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm= new Vue({
el:'#root',
data:{
name: '江西-南昌'
},
methods:{
showInfo:function (e){
// e.preventDefault(); // 阻止默认事件
// e.stopPropagation(); // 阻止事件冒泡
console.log(e.target);
},
showMsg(msg) {
console.log(msg);
},
demo() {
console.log('@@@ demo 滚动事件开始');
for (let i = 0; i < 1000; i++) {
console.log('#');
}
console.log('demo 滚动事件结束');
},
ClickInfo(e) {
console.log('点击提示输入1');
console.log(e.target);
console.log(this === vm ? 'this 指向 vm' : 'this 指向 事件触发的元素');
},
showClickInfo(e, msg) {
console.log('点击提示输入2');
console.log(e.target);
console.log('接收的参数是: ' + msg);
}
}
})
</script>
</body>
</html>

这段代码是 Vue.js 事件处理机制的集大成者。它不仅涵盖了上一轮讨论的键盘事件,更深入展示了 Vue 强大的事件修饰符系统。
通过这段代码,我们可以看到 Vue 如何通过简单的指令修饰,解决了原生 DOM 事件处理中繁琐的兼容性判断和逻辑控制问题。结合你的代码,我将从修饰符的深度解析、事件流的控制以及性能优化三个维度来为你解读。
🛠️ 核心机制:事件修饰符 (Event Modifiers)
Vue 的修饰符是 Vue.js 事件系统中最优雅的设计,它们通过链式调用的方式,让模板代码直接表达意图,而不需要在方法逻辑中混杂 DOM 操作。
1. 阻断与限制类
| 修饰符 | 代码示例 | 核心作用 | 实际应用场景 |
|---|---|---|---|
.prevent |
@click.prevent |
阻止默认行为 | 表单提交验证、A 标签拦截 |
.stop |
@click.stop |
阻止事件冒泡 | 嵌套元素点击、模态框遮罩 |
.once |
@click.once |
事件只触发一次 | 防止重复提交、一次性引导 |
2. 逻辑与性能类
| 修饰符 | 代码示例 | 核心作用 | 实际应用场景 |
|---|---|---|---|
.self |
@click.self |
仅目标元素触发 | 点击模态框外部关闭(点击内容不关闭) |
.capture |
@click.capture |
捕获模式触发 | 需要父元素先于子元素响应的交互 |
.passive |
@scroll.passive |
立即执行默认行为 | 移动端滚动优化,解决卡顿 |
🌊 事件流:捕获与冒泡
代码中关于 .box1、.box2 和 .btn3 的嵌套结构,完美演示了 DOM 事件流的两种模式:
1. 默认的冒泡模式 (Bubbling)
在 .box2 和 .btn3 的代码中:
- 逻辑:当你点击按钮(
.btn3)时,事件会先触发按钮的点击,再传递给父级(.box2),最后传递给祖父级(.box1)。 - 代码:
@click(默认写法)。
2. 捕获模式 (Capturing)
在 .box1 的代码中:
- 逻辑:加上
.capture修饰符后,事件流的方向反转。Vue 会先检查父级(.box1)是否有捕获监听,如果有,先执行父级,然后再向下传递给子级(.box2->.btn3)。 - 代码:
@click.capture。 - 输出顺序:点击按钮时,控制台会先输出
box1,再输出box2,最后输出btn3。
🚀 性能优化:.passive 的妙用
代码中的 <ul @scroll.passive="demo"> 是一个非常高级的用法,它解决了 Web 开发中一个经典的性能痛点。
- 问题背景:在移动端,浏览器在触发
scroll事件前,会等待事件回调执行完,以判断开发者是否会调用event.preventDefault()来阻止滚动。如果回调函数里有大量计算(如代码中的for循环),页面滚动就会变得极其卡顿。 - Vue 的解法:
.passive修饰符告诉浏览器:“我不会阻止滚动,请你直接滚动,不要等我”。 - 效果:加上
.passive后,滚动条会立即响应,无需等待demo函数里的循环执行完毕,极大地提升了用户体验。
⚠️ 关于 this 的再次强调
在 ClickInfo 方法中,你再次验证了 Vue 的 this 绑定机制:
- 原生 JS:
this指向触发事件的 DOM 元素(e.target)。 - Vue Methods:Vue 自动将
this绑定为 Vue 实例(vm)。这意味着你可以在ClickInfo中直接通过this.name访问数据,或者调用this上的其他方法,而不需要手动绑定this。
📌 总结与建议
这段代码是 Vue 事件处理的最佳实践指南。在实际开发中,建议你:
- 善用连写:如
@click.prevent.self,既阻止默认行为又防止冒泡,代码极其精简。 - **移动端必加
.passive**:在处理touchstart、touchmove、scroll等高频事件时,如果不需要阻止默认行为,请务必加上.passive,这是提升流畅度的关键。 - 理解事件流:
.capture和.self虽然不常用,但在处理复杂的嵌套组件交互(如日历组件、拖拽库)时是解决问题的利器。
计算属性
计算属性:
1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!--v-model双向绑定-->
姓:<input type="text" v-model="firstName"/>
<br/><br/>
名:<input type="text" v-model="lastName"/>
<br/><br/>
全名-computed实现: <span>{{ fullName }}</span>
<br/><br/>
全名: <input type="text" v-model="test" placeholder="请输入全名"/>
<button @click="changeFullName">修改</button>
<br/><br/>
<!-- 全名-menthods实现: <span>{{ fullName() }}</span> -->
</div>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName: '三',
test: '测--试'
},
computed: {
fullName: {
get() {
console.log('fullNameByComputed get()被调用了');
return this.firstName + '--' + this.lastName;
},
set(val) {
console.log('fullNameByComputed set()被调用了');
const [firstName, lastName] = val.split('--');
this.firstName = firstName;
this.lastName = lastName;
console.log('fullNameByComputed set()被调用了');
}
}
},
//简写形式
//前提:计算属性只考虑读取不考虑修改 set丢了
//简写计算书写为一个函数(这个函数当成getter使用), 注意不要写箭头函数
//执行完getter之后,vm直接保存返回的数据为fullName属性的属性值,此时vm.fullName不是函数而是一个确切的计算值
// computed: {
// fullName: function() {
// console.log('fullName()被调用了');
// return this.firstName + '--' + this.lastName;
// }
// },
methods: {
changeFullName() {
this.fullName = this.test;
},
// fullName() {
// console.log('fullName()被调用了');
// return `${this.firstName.slice(0, 4)} -- ${this.lastName}`;
// }
}
});
</script>
</body>
</html>
这段代码非常清晰地展示了 Vue 中计算属性的核心概念。通过“姓”和“名”两个输入框动态拼接“全名”,完美体现了 computed 相比于普通方法的优势。
结合你的代码,我来为你深入拆解 computed 的工作原理和它为什么是 Vue 的“性能利器”:
🧮 什么是计算属性?
计算属性本质上是一个“会自动更新的值”。
它看起来像一个属性(在模板中使用时不需要加括号 {{ fullName }}),但背后其实是一个函数,用来根据其他数据计算出新数据。
⚙️ 工作原理:依赖追踪与缓存
这是 computed 和 methods 最大的区别。
- 你的代码逻辑:
fullName依赖于this.firstName和this.lastName。- 当页面初次渲染时,Vue 会执行
fullName函数,得到结果“张–三”。 - Vue 会悄悄记住:“
fullName是由firstName和lastName算出来的”。
- 缓存机制(关键点):
- 假设你在页面上多次使用
{{ fullName }}(比如用了 3 次),Vue 只会执行 1 次fullName函数,然后把结果复用 3 次。 - 只有当
firstName或lastName的值发生变化时,Vue 才会“作废”旧缓存,并在下一次读取fullName时重新执行函数计算新值。
- 假设你在页面上多次使用
- **对比
methods**:- 如果你用
methods实现,每次调用{{ fullName() }},函数都会无条件执行,不管数据变没变。如果计算逻辑很复杂,这会浪费大量性能。
- 如果你用
📝 简写形式
你的代码使用了 computed 的简写形式。
- 适用场景:只需要读取计算属性的值,不需要修改它。
- 写法:直接把
get函数写成fullName: function() {}。 - 注意:简写形式的函数体里不能有
this.xxx = yyy这种赋值操作(除非是修改其他无关数据),因为简写默认只有get,没有set。
🎬 代码运行流程图解
- 初始化:Vue 实例创建,
firstName为“张”,lastName为“三”。 - 渲染:模板遇到
{{ fullName }}。 - 求值:Vue 发现
fullName是计算属性,于是执行函数,控制台打印'fullName()被调用了',返回“张–三”。 - 缓存:Vue 把“张–三”存起来。
- 后续渲染:如果页面其他地方还有
{{ fullName }},Vue 直接拿缓存的值,不再打印'fullName()被调用了'。 - 更新:当你在输入框修改“姓”时,
firstName变了。Vue 检测到依赖变化,标记fullName为“脏数据”。 - 重新求值:当下次渲染需要
fullName时,Vue 再次执行函数,打印日志,得到新值。
💡 总结
computed 是 Vue 的“智能缓存器”。它利用 Getter 来派生数据,并利用 依赖追踪 来保证只有在必要时才重新计算。这让你的代码既声明式(像属性一样用),又高性能(自动缓存)。
监视属性
监视属性watch:
1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
2.监视的属性必须存在,才能进行监视!!
3.监视的两种写法:
(1).new Vue时传入watch配置
(2).通过vm.$watch监视
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>天气案例</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>今天天气很 {{ info }}</h1>
<button @click="changeWeather">
切换天气
</button>
<h2>numbers.a: {{ numbers.a }}</h2>
<button @click="numbers.a++">
a++
</button>
<h2>numbers.b: {{ numbers.b }}</h2>
<button @click="numbers.b++">
b++
</button>
<p> 测试vue自身监测数据属性 </p>
<h3>numbers.c.d: {{ numbers.c.d }}</h3>
<button @click="numbers.c.d++">
d++
</button>
<h3>numbers.c.e: {{ numbers.c.e }}</h3>
<button @click="numbers.c.e++">
e++
</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
isHot: true,
numbers: {
a: 100,
b: 200,
c: {
d: 300,
e: 400
}
}
},
//计算属性
computed: {
info(){
return this.isHot ? '炎热' : '凉爽';
}
},
//改变模版数据的方法
methods:{
changeWeather(){
this.isHot = !this.isHot;
}
},
//监视属性
// watch: {
// // isHot: {
// // // immediate: true, // 初始化时立即调用handler
// // // handler 接收两个参数: 一个是新值, 一个是旧值
// // // 当 isHot 变化时, 调用 handler
// // handler(newVal, oldVal){
// // console.log('ishot 被修改了');
// // console.log('newVal: ', newVal);
// // if (newVal) {
// // console.log('天气炎热');
// // } else {
// // console.log('天气凉爽');
// // }
// // console.log('oldVal: ', oldVal);
// // if (oldVal) {
// // console.log('之前天气炎热');
// // } else {
// // console.log('之前天气凉爽');
// // }
// // }
// // },
// isHot(newVal, oldVal) { //简写的前提是watch属性不需要immediate和deep属性的时候
// console.log('ishot 被修改了');
// console.log('newVal: ', newVal);
// if (newVal) {
// console.log('天气炎热');
// } else {
// console.log('天气凉爽');
// }
// console.log('oldVal: ', oldVal);
// if (oldVal) {
// console.log('之前天气炎热');
// } else {
// console.log('之前天气凉爽');
// }
// },
// numbers: {
// deep: true,
// handler() {
// console.log('numbers 发生改变');
// }
// }
// }
});
// 完整写法
vm.$watch('isHot', {
deep: true,
immediate: true,
handler(newVal, oldVal) {
console.log('ishot 被修改了');
console.log('newVal: ', newVal);
if (newVal) {
console.log('天气炎热');
} else {
console.log('天气凉爽');
}
console.log('oldVal: ', oldVal);
if (oldVal) {
console.log('之前天气炎热');
} else {
console.log('之前天气凉爽');
}
}
})
</script>
</body>
</html>
这段代码是一个非常经典的 Vue.js 侦听属性(Watch) 教学案例。它清晰地展示了 Vue 如何自动追踪数据变化,并演示了浅层侦听与深层侦听的区别。
结合你的代码(目前 watch 选项被注释掉了,但逻辑在 $watch 中),我来为你深度解析 Vue 的“监测机制”:
1. 核心机制:vm.$watch
虽然你在 new Vue 里注释掉了 watch,但你在实例创建后使用了 vm.$watch。这两者的效果是完全一样的。
- 侦听目标:
'isHot' - 配置项:
immediate: true:立即执行。通常 Watcher 是在数据变化后才执行,加上这个参数,页面加载完毕初始化时就会立刻执行一次handler。deep: true:深度侦听。对于对象或数组,如果内部属性变化,也能监测到(后面会重点讲这个)。
- **回调函数 (handler)**:当被侦听的数据变化时,执行这里的逻辑。
2. Vue 的监测奥秘:numbers 对象
页面上有一堆按钮在修改 numbers 对象里的值(a、b、c.d、c.e)。这里隐藏了一个 Vue 2 的重要特性:
❓ 问题:为什么修改 numbers.c.d 能被监测到?
如果你在 watch 里写 numbers: { handler() {} },你会发现:
- 修改
numbers.a:能触发侦听。 - 修改
numbers.c.d:能触发侦听。
原因:Vue 2 的响应式系统是基于 Object.defineProperty 的递归遍历。当你把 numbers 对象交给 Vue 管理时,Vue 会立刻遍历它所有的嵌套属性(a, b, c, d, e),把它们都转换成 getter/setter。所以,无论你改到哪一层,Vue 都知道“数据变了”。
⚠️ 注意:deep: true 在这里是“多余”的?
在你的 $watch 代码中,你给 isHot 用了 deep: true,这没问题,因为 isHot 是布尔值,不需要深度遍历。
但是,如果你去侦听 numbers 对象:
- **如果不加
deep**:修改numbers.a会触发,修改numbers.c.d也会触发(因为 Vue 默认就追踪到了)。 - **加了
deep**:Vue 会强制遍历对象的每一层。对于复杂对象,这会消耗性能。
结论:对于 Vue 实例中 data 定义的深层对象,通常不需要手动加 deep: true,Vue 已经帮你做好响应式了。
3. 计算属性 vs. 侦听属性
你的代码里同时有 computed 和 watch,正好做个对比:
- **
computed(计算属性)**:- 用途:派生数据。比如
fullName是由firstName和lastName算出来的。 - 特点:有缓存,只有依赖的数据变了才会重新计算。
- 用途:派生数据。比如
- **
watch(侦听属性)**:- 用途:异步操作 或 复杂逻辑。比如监听搜索关键词变化,然后去发 Ajax 请求;或者监听数据变化后做一个复杂的动画。
- 特点:没有缓存,只要数据变(或初始化),就会执行回调。
4. 代码运行预测
当你运行这段代码时,控制台会发生以下事情:
- 页面加载:因为
immediate: true,handler会立刻执行一次。newVal是true(天气炎热)。oldVal是undefined(因为是初始化,没有旧值)。
- 点击“切换天气”:
isHot变为false。handler再次执行。newVal是false(天气凉爽)。oldVal是true(之前天气炎热)。
- **点击
a++/d++**:控制台会打印numbers 发生改变(如果你取消注释watch里的numbers配置)。
📌 总结
这段代码展示了 Vue 的响应式核心:
- 自动追踪:Vue 在初始化时就把数据劫持了,不管你怎么改
numbers的深层属性,它都能感知。 - 侦听器:适合做“数据变化后的副作用处理”(比如发请求、改 DOM、打印日志)。
- 深度侦听:虽然好用,但有性能开销,Vue 默认的对象监测已经很强大了。
计算属性 && 监视属性
computed和watch之间的区别:
1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,
这样this的指向才是vm 或 组件实例对象。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!--v-model双向绑定-->
姓:<input type="text" v-model="firstName"/>
<br/><br/>
名:<input type="text" v-model="lastName"/>
<br/><br/>
全名: <span>{{ fullName }}</span>
<br/><br/>
test: <input type="text" v-model="test" placeholder="请输入全名"/>
<button @click="changeFullName">修改</button>
<br/><br/>
<!-- 全名-menthods实现: <span>{{ fullName() }}</span> -->
</div>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName: '三',
fullName: 'zhang--san',
test: '测--试'
},
watch: {
firstName(newF) {
setTimeout(() => {
this.fullName = newF + '--' + this.lastName;
}, 1000);
},
lastName(newL) {
setTimeout(() => {
this.fullName = this.firstName + '--' + newL;
}, 1000);
}
},
// computed: {
// fullName: {
// get() {
// console.log('fullNameByComputed get()被调用了');
// return this.firstName + '--' + this.lastName;
// },
// set(val) {
// console.log('fullNameByComputed set()被调用了');
// const [firstName, lastName] = val.split('--');
// this.firstName = firstName;
// this.lastName = lastName;
// console.log('fullNameByComputed set()被调用了');
// }
// }
// },
//简写形式
//前提:计算属性只考虑读取不考虑修改 set丢了
//简写计算书写为一个函数(这个函数当成getter使用), 注意不要写箭头函数
//执行完getter之后,vm直接保存返回的数据为fullName属性的属性值,此时vm.fullName不是函数而是一个确切的计算值
// computed: {
// fullName: function() {
// console.log('fullName()被调用了');
// return this.firstName + '--' + this.lastName;
// }
// },
methods: {
changeFullName() {
this.firstName = this.test.split('--')[0];
this.lastName = this.test.split('--')[1];
},
// fullName() {
// console.log('fullName()被调用了');
// return `${this.firstName.slice(0, 4)} -- ${this.lastName}`;
// }
}
});
</script>
</body>
</html>
computed和watch的本质区别和使用场景。
深入剖析一下为什么说 “watch 能做异步,computed 不能”,以及 “箭头函数的 this 指向” 这两个原则。
1. computed vs watch:同步与异步的鸿沟
你的代码完美地证明了这一点。
computed 的局限性(纯同步)
- 机制:
computed是一个纯函数。给定相同的输入(firstName和lastName),它必须立刻返回相同的输出。 - 限制:它不能包含“等待”逻辑。你不能在
computed里写setTimeout,因为计算属性必须立即返回一个值,否则页面渲染就会卡住。 - 适用:简单的拼接、数学运算、过滤数组等。
watch 的灵活性(可异步)
- 机制:
watch是一个监听器。它不负责返回值,它负责“执行任务”。 - 优势:在你的代码中,
watch里用了setTimeout。这意味着:- Vue 不需要立刻得到结果。
- Vue 只是“看着”数据变化,然后在后台开启一个定时器。
- 1秒后,定时器回调函数执行,再去修改数据。
- 适用:搜索建议(输入后等用户停顿再发请求)、表单验证、路由监听等。
2. 关于 this 指向的两个原则
你的代码也是这两个原则的最佳实践范例。
原则一:Vue 管理的函数,用普通函数
- 例子:
watch里的firstName(newF) { ... }。 - 分析:
firstName是 Vue 帮你调用的。Vue 内部会把this绑定为 Vue 实例(vm)。如果你写成箭头函数firstName: (newF) => { ... },虽然this也能指向vm(因为外层是全局作用域),但在严格模式下可能会出问题,且不符合 Vue 的设计规范。使用普通函数是最稳妥、最符合直觉的写法。
原则二:非 Vue 管理的函数,用箭头函数
- 例子:
setTimeout(() => { ... }, 1000)。 - 分析:
setTimeout是浏览器的 API,不是 Vue 调用的。- 如果你写成普通函数
setTimeout(function() { this.fullName = ... }, 1000),这里的this会指向window(非严格模式)或undefined(严格模式),导致报错。 - **箭头函数没有自己的
this**,它会向外层作用域(Vue 的watch函数)查找。在watch函数里,this指向vm,所以箭头函数里的this也指向vm。
3. 代码运行流程图解
- 初始化:
firstName是“张”,lastName是“三”,fullName是“zhang–san”。 - 输入:你在“姓”的输入框输入“李”。
- 触发:
data.firstName变为“李”,触发watch.firstName回调。 - 异步:
watch启动一个 1 秒的倒计时。 - 更新:1 秒后,倒计时结束,执行回调,将
fullName修改为“李–三”。 - 渲染:页面上的“全名”从旧值变为“李–三”。
总结
你的理解完全正确:
- 用
computed:当你需要一个实时的、基于数据计算出来的值,且这个计算是同步的。 - 用
watch:当你需要在数据变化时执行一段代码,且这段代码可能包含异步操作(如setTimeout、axios请求)或复杂的业务逻辑。
这段代码就是 watch 处理异步场景(模拟网络请求延迟)的经典例子。
绑定样式
:class=”XXX”
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue绑定样式</title>
<style>
.basic{
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy{
border: 4px solid red;;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg,yellow,pink,orange,yellow);
}
.sad{
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal{
background-color: skyblue;
}
.atguigu1{
background-color: yellowgreen;
}
.atguigu2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
.atguigu3{
border-radius: 20px;
}
</style>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<hr/><!--:class 绑定class样式字符串写法 适用于样式的类名不确定需要动态琢磨的状况-->
<div class="basic" :class="mood" @click="changeMood">{{ name }}</div>
<hr/><!--:class 绑定class样式数组写法 适用于要绑定的样式个数不确定,名字也不确定的状况-->
<div class="basic" :class="classArr">{{ name }}</div>
<hr/><!--:class 绑定class样式对象写法 适用于要绑定的样式个数确定,名字确定,但动态决定要不要用的状况-->
<div class="basic" :class="classObj">{{ name }}</div>
<hr/>
</div>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data: {
name:'test',
mood: 'normal',
classArr: ['atguigu1','atguigu2','atguigu3'],
classObj: {
atguigu1: true,
atguigu2: true,
atguigu3: true
}
},
methods:{
changeMood(){
//vue绑定事件
//不要亲手操作dom
//随机切换心情
const moods = ['happy','sad','normal'];
const random = Math.floor(Math.random() * moods.length);
this.mood = moods[random];
}
}
});
</script>
</body>
</html>
这段代码是 Vue.js 动态样式绑定 的教科书级范例。它清晰地展示了如何通过 :class 指令,用三种不同的数据结构(字符串、数组、对象)来动态控制 DOM 的 CSS 类名。
结合你的注释和代码,我来为你深度拆解这三种写法的适用场景和底层逻辑:
1. 字符串写法 (v-bind:class / :class)
代码:
<div class="basic" :class="mood" @click="changeMood">{{ name }}</div>
data: {
mood: 'normal'
},
methods: {
changeMood() {
const moods = ['happy','sad','normal'];
const random = Math.floor(Math.random() * moods.length);
this.mood = moods[random]; // 动态改变字符串值
}
}
- 原理:直接将
data中的一个字符串变量绑定到class上。 - 效果:HTML 渲染为
<div class="basic happy">(假设 mood 为 happy)。 - 适用场景:“状态切换”。比如心情切换、主题切换(白天/黑夜模式)、选项卡高亮。类名是动态的,且互斥。
- 关键点:
class可以和普通的class共存(basic一直存在,mood动态变化)。
2. 数组写法 (:class)
代码:
<div class="basic" :class="classArr">{{ name }}</div>
data: {
classArr: ['atguigu1','atguigu2','atguigu3']
}
- 原理:绑定一个数组,数组的每一项都是一个字符串类名。
- 效果:HTML 渲染为
<div class="basic atguigu1 atguigu2 atguigu3">。 - 适用场景:“组合式样式”。当样式的个数不确定,或者名字需要通过循环/接口动态生成时。
- 动态性:你可以通过
push、splice等数组方法动态修改classArr,DOM 会自动更新。
3. 对象写法 (:class)
代码:
<div class="basic" :class="classObj">{{ name }}</div>
data: {
classObj: {
atguigu1: true,
atguigu2: true,
atguigu3: true
}
}
- 原理:绑定一个对象。键 (key) 是类名,值 (value) 是布尔值。
- 效果:只有值为
true的键,才会被渲染到页面上。 - 适用场景:“开关式样式”。样式的个数和名字是确定的,但需要根据业务逻辑(如是否选中、是否禁用)来决定是否应用。
- 优势:逻辑非常清晰,一眼就能看出哪个样式开,哪个样式关。
📌 总结与对比
| 写法 | 数据类型 | 核心特点 | 典型应用场景 |
|---|---|---|---|
| 字符串 | String |
值直接对应类名 | 状态切换(如:active, error, success) |
| 数组 | Array |
多个类名的列表 | 动态组合(如:从接口获取标签样式列表) |
| 对象 | Object |
键值对,值为布尔值 | 条件渲染(如:{ bold: isBold, hidden: isHidden }) |
💡 补充建议
三元表达式:
你也可以在数组或对象中使用三元表达式:<!-- 如果 isActive 为真,应用 activeClass,否则应用 defaultClass --> <div :class="[isActive ? activeClass : defaultClass]"></div>混合使用:
Vue 允许你同时使用静态class和动态:class,它们会自动合并,不会冲突。**内联样式绑定 (
:style)**:
虽然这段代码没展示,但:style的用法与:class类似,通常使用对象写法({ color: activeColor, fontSize: fontSize + 'px' })来动态修改内联样式。
你的代码注释写得非常棒,准确地概括了每种写法的适用场景!
条件渲染
v-if=”XXX” v-else-if=”XXX” v-else=”XXX”
v-show=”XXX”
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>条件渲染</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!--
条件渲染:
1.v-if
写法:
(1).v-if="表达式"
(2).v-else-if="表达式"
(3).v-else="表达式"
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
2.v-show
写法:v-show="表达式"
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
-->
<div id="root">
<!--v-show要能转换为一个布尔值 v-show条件渲染-->
<!-- <h2 v-show="a">欢迎来到{{ name }}</h2>-->
<!-- <h2 v-show="1===1">欢迎来到{{ name }}</h2>-->
<!-- <button @click='a = !a'>{{ a ? '隐藏': '显示' }}</button>-->
<!--使用v-if来进行条件渲染 -->
<!-- <h2 v-if="1">欢迎来到{{ name }}</h2>-->
<!--v-if v-else-if v-else记住不能打断,要连着写-->
<!-- <h2>当前的n值是{{ n }}</h2>-->
<button @click="n++">点我n+1</button>
<!-- <div v-if="n === 1">angular</div>-->
<!-- <div v-else-if="n === 2">react</div>-->
<!--<!– <div>111</div>–>-->
<!-- <div v-else-if="n === 3">vue js</div>-->
<!-- <div v-else>not found</div>-->
<!--v-if与template的配合使用-->
<template v-if="n === 1">
<h2>你好</h2>
<h2>shanghai</h2>
<h2>shenzhen</h2>
</template>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{
name: 'shanghai',
n:0
}
})
</script>
</body>
</html>
这段代码是 Vue.js 条件渲染 的完整指南。它清晰地展示了 v-if 和 v-show 这对“双胞胎”的区别,以及 v-if 在复杂逻辑和模板分组中的用法。
结合你的注释和代码,我来为你深度拆解这两个指令的底层逻辑和使用策略:
1. v-if vs v-show:移除 vs 隐藏
这是 Vue 面试和实际开发中最常遇到的选择题。
v-if:真正的“条件渲染”
- 机制:惰性渲染。如果初始条件为假,什么也不做;为真时,才会创建元素并添加到 DOM 中。切换时,会销毁/重建组件。
- 本质:通过添加/移除 DOM 元素来控制显示。
- 代码验证:打开浏览器开发者工具,当你使用
v-if时,条件为假的元素在 HTML 结构中完全消失。
v-show:CSS 的“开关”
- 机制:强制渲染。无论条件真假,元素都会被创建并保留在 DOM 中。通过
display: none样式来控制视觉上的隐藏。 - 本质:通过CSS 样式控制显示。
- 代码验证:使用
v-show时,即使元素看不见了,它依然存在于 HTML 结构中,只是多了一个style="display: none;"。
2. 性能与场景选择
使用 v-if 的场景
- 切换频率低:因为切换需要销毁和重建 DOM,开销较大。
- 包含复杂组件:比如模态框、弹窗,内部可能有大量逻辑和子组件,关闭时销毁可以释放内存。
- 首次渲染:如果初始状态是隐藏,
v-if不会渲染,节省了初始渲染时间。
使用 v-show 的场景
- 切换频率高:只是切换 CSS,性能极高。
- 简单元素:不需要频繁销毁重建的开销。
3. v-if 的进阶用法
1. v-else-if 与 v-else
- 逻辑:完全等同于 JavaScript 的
if...else if...else。 - 关键规则:不能被打断。必须连在一起写。如果你在
v-if和v-else中间插入了一个普通 HTML 标签,v-else就会失效,因为它找不到对应的“兄弟”v-if了。 - 你的代码测试:你注释掉的那段
n === 1、n === 2、n === 3就是典型的多分支判断。
2. <template> 标签
- 问题:如果你想要同时切换多个元素,是不是要给每个元素都写一遍
v-if? - 解法:使用
<template>。它是一个不可见的包装器。 - 你的代码分析:
- 当
n === 1时,三个<h2>标签会同时显示。 - 当
n !== 1时,这三个标签会被整体移除。 - 注意:
<template>只能配合v-if,不能配合v-show(因为v-show是 CSS 控制,<template>无法被渲染,也就无法应用 CSS)。
- 当
4. 关于“获取元素”的备注
你注释中提到的那句“使用 v-if 时,元素可能无法获取到,而使用 v-show 一定可以获取到”非常关键。
- **
v-show**:元素始终在 DOM 树里。你可以随时通过document.getElementById或ref获取到它。 - **
v-if**:元素可能根本不存在。如果你在v-if="false"的时候尝试获取该 DOM 元素,会得到null。这在操作 DOM 或使用第三方库时需要特别注意。
总结
v-if:“有无”**的问题。适合**懒加载**、节省内存、一次性展示**。v-show:“显隐”**的问题。适合**频繁切换**、保留状态**。
在你的代码中,<template v-if> 的写法展示了如何优雅地控制一组元素的显示,这是编写复杂模板时非常实用的技巧。
列表渲染
收集表单数据
过滤器
内置指令
自定义指令
生命周期
非单文件组件
单文件组件
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 jungle8884@163.com