Vue2学习笔记(上)
一、Vue简介
1.Vue是什么?
一套用于构建用户界面的渐进式的JS框架
2.Vue的特点
1. 采用组件化模式,提高代码复用率,且让代码更好维护
2. 声明式编码,让编码人员无需直接操作DOM,提高开发效率
原生JS使用的是命令式编码
3. 使用虚拟DOM+优秀的Diff的算法,尽量复用DOM节点
将数据变换成虚拟DOM,采用Diff算法对比之前的虚拟DOM(比较),然后将新的虚拟DOM编写在页面的真实DOM。
3.JS知识初识
- ES6的语法规范
- ES6的模块化
- 包管理器
- 原型、原型链
- 数组的常用方法
- axios
- promise
4.Vue 模板语法:
1.插值语法:
- 功能:用于解析标签体的内容
- 写法:{{xxx}},xxx是js的表达式,且直接可以获取data中的所有属性
2.指令写法:
- 功能:
用于解析标签(包括:标签属性、标签体内容、绑定事件……) - 举例:
v-bind :href = “xxx”可以简写成 ::href = “xxx”,xxx同样是要写js表达式,且可以读取到data中的所有属性 - 备注:
Vue中有很多的指令,且形式都是:v-xxx,此处我们只是一v-bind举例子。
5.Vue初识:
1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue的语法
3.root容器里的代码被称为【Vue模版】
4.Vue的实例和容器是一一对应的
5.真实开发的过程中,只有一个Vue的实例,并且会配合着组件一起使用
6.{{xxx}}中的xxx需要写JS代码,且xxx可以自动读取到data中的所有属性
7.data中的数据发生改变,那么模板中用到该数据的地方也会自动更新
注意:
区分js表达式和js代码(语句)
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
(1)a、(2)a+b、(3)demo(1)、(4)x===y?‘a’:‘b’; - js代码(语句):
(1)if(){}、(2)for(){}
6.数据绑定
1.单向绑定(v-bind):
数据只能从data流向页面。
2.双向绑定(v-model):
数据不仅能从data流向页面,还可以从页面流向data。
备注:
- 1.双向绑定一般都应用在表单类元素上(如:input、select等)
- 2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
><div id="root"><!-- 普通写法 --><!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>双向数据绑定:<input type="text" v-model:value="name"><br/> --><!-- 简写 -->单向数据绑定:<input type="text" :value="name"><br/>双向数据绑定:<input type="text" v-model="name"><br/><!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 --><!-- <h2 v-model:x="name">你好啊</h2> -->
> </div>
7.el和data的两种写法
1.el
- (1).new Vue时候配置el属性。
- (2).先创建Vue实例,随后再通过***vm.$mount(‘#root’)***指定el的值。
/* const v = new Vue({//el:'#root', //第一种写法data:{name:'尚硅谷'}})console.log(v)v.$mount('#root') //第二种写法 */
2.data
- (1).对象式
- (2).函数式
//data的两种写法new Vue({el:'#root',//data的第一种写法:对象式/* data:{name:'尚硅谷'} *///data的第二种写法:函数式data(){console.log('@@@',this) //此处的this是Vue实例对象return{name:'尚硅谷'}
特别的:
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
3.一个重要的原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
8.MVVM模型
- M:模型(Model) :data中的数据
- V:视图(View) :模板代码
- VM:视图模型(ViewModel):Vue实例
观察发现:
- 1.data中所有的属性,最后都出现在了vm身上。
- 2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。
9.数据代理
1. Object.defineProperty方法
Object.defineProperty(person,'age',{// value:18,// enumerable:true, //控制属性是否可以枚举,默认值是false// writable:true, //控制属性是否可以被修改,默认值是false// configurable:true //控制属性是否可以被删除,默认值是false}
当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值get(){console.log('有人读取age属性了')return number},当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值set(value){console.log('有人修改了age属性,且值是',value)number = value}
2.数据代理
通过一个对象代理对另一个对象中属性的操作(读/写)
let obj = {x:100}
let obj2 = {y:20
Object.defineProperty(obj2,'x',{get(){return obj.x},set(value){obj.x = value}
})
3.Vue中的数据代理
- Vue中的数据代理:
- 通过vm对象来代理data对象中属性的操作(读/写)
- Vue中数据代理的好处:
- 更加方便的操作data中的数据
- 基本原理:
- 通过Object.defineProperty()把data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter。在getter/setter内部去操作(读/写)data中对应的属性。
数据代理示例图
10.事件处理
1. 事件的基本使用
1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
2.事件的回调需要配置在methods对象中,最终会在vm上;
3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
5.@click=“demo” 和 @click=“demo($event)” 效果一致,但后者可以传参;
<button @click="showInfo1">点我提示信息1(不传参)</button><button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
const vm = new Vue({el:'#root',data:{name:'尚硅谷',},methods:{showInfo1(event){// console.log(event.target.innerText)// console.log(this) //此处的this是vmalert('同学你好!')},showInfo2(event,number){console.log(event,number)// console.log(event.target.innerText)// console.log(this) //此处的this是vmalert('同学你好!!')}}})
2.事件修饰符
1.prevent:阻止默认事件(常用);
2.stop:阻止事件冒泡(常用);
3.once:事件只触发一次(常用);
4.capture:使用事件的捕获模式;
5.self:只有event.target是当前操作的元素时才触发事件;
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
<!-- 准备好一个容器--><div id="root"><h2>欢迎来到{{name}}学习</h2><!-- 阻止默认事件(常用) --><a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a><!-- 阻止事件冒泡(常用) --><div class="demo1" @click="showInfo"><button @click.stop="showInfo">点我提示信息</button><!-- 修饰符可以连续写 --><!-<ahref="http://www.atguigu.com"@click.prevent.stop="showInfo">点我提示信息</a> --></div><!-- 事件只触发一次(常用) --><button @click.once="showInfo">点我提示信息</button><!-- 使用事件的捕获模式 --><div class="box1" @click.capture="showMsg(1)">div1<div class="box2" @click="showMsg(2)">div2</div></div><!-- 只有event.target是当前操作的元素时才触发事件; --><div class="demo1" @click.self="showInfo"><button @click="showInfo">点我提示信息</button></div><!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; --><ul @wheel.passive="demo" class="list"><li>1</li><li>2</li><li>3</li><li>4</li></ul></div>
你还可以同时使用多个修饰符。例如,@click.prevent.stop 同时阻止默认行为和事件冒泡。
<template><button @click.prevent.stop="handleClick">点击我</button>
</template><script>
export default {methods: {handleClick() {alert('事件已被阻止');}}
}
</script>
3.键盘事件
- 回车 => enter ;
- 删除 => delete (捕获“删除”和“退格”键);
- 退出 => esc;
- 空格 => space;
- 换行 =>tab (特殊,必须配合keydown去使用) ;
- 上 => up ;
- 下 => down;
- 左 => left;
- 右 => right。
注意:
1.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
2.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2).配合keydown使用:正常触发事件。
3.也可以使用keyCode去指定具体的按键(不推荐)4.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
11.计算属性
(一).概念:
1.定义:
要用的属性不存在,要通过已有属性计算得来。
2.原理:
底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
- (1)初次读取时会执行一次。
- (2)当依赖的数据发生改变时会被再次调用。
4.优势:
与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
- 计算属性最终会出现在vm上,直接读取使用即可。
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
(二).姓名案例-插值语法实现
<body><!-- 准备好一个容器--><div id="root">姓:<input type="text" class="text" v-model:value = "firstname"><br>名:<input type="text" class="text" v-model = "lastname"><br>全名: <span>{{firstname.slice(0,3)}}-{{lastname}}</span>//只收集姓的前三位</div></body><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。new Vue({el:'#root',data:{firstname:'zhang',lastname:'san'}})</script>
(三).姓名案例-method方法实现
<body><!-- 准备好一个容器--><div id="root">姓:<input type="text" v-model="firstName"> <br/><br/>名:<input type="text" v-model="lastName"> <br/><br/>全名:<span>{{fullName()}}</span></div></body><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。new Vue({el:'#root',data:{firstName:'张',lastName:'三'},methods: {fullName(){console.log('@---fullName')return this.firstName + '-' + this.lastName}},})</script>
(四).姓名案例-计算属性实现
<div id="root">姓:<input type="text" v-model="firstName"> <br/><br/>名:<input type="text" v-model="lastName"> <br/><br/>测试:<input type="text" v-model="x"> <br/><br/>全名:<span>{{fullName}}</span> <br/><br/><!-- 全名:<span>{{fullName}}</span> <br/><br/>全名:<span>{{fullName}}</span> <br/><br/>全名:<span>{{fullName}}</span> --></div></body><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el:'#root',data:{firstName:'张',lastName:'三',x:'你好'},methods: {demo(){}},computed:{fullName:{//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。get(){console.log('get被调用了')// console.log(this) //此处的this是vmreturn this.firstName + '-' + this.lastName},//set什么时候调用? 当fullName被修改时。set(value){console.log('set',value)const arr = value.split('-')this.firstName = arr[0]this.lastName = arr[1]}}}})</script>
(五).计算属性简写
注意:
只有在只读取数据进行调用的时候才能简写,当对数据进行修改的时候,并不能进行简写计算属性方法
<script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el:'#root',data:{firstName:'张',lastName:'三',},computed:{//完整写法/* fullName:{get(){console.log('get被调用了')return this.firstName + '-' + this.lastName},set(value){console.log('set',value)const arr = value.split('-')this.firstName = arr[0]this.lastName = arr[1]}} *///简写fullName(){console.log('get被调用了')return this.firstName + '-' + this.lastName}}})</script>
在 Vue 中,计算属性(computed properties) 是一个非常强大的功能,用于基于已有的响应式数据计算出新的值。计算属性是 Vue 提供的一种声明式方法,用来简化模板中的逻辑,避免重复计算,并且在依赖的数据变化时自动重新计算。
(六) chatgpt总结计算属性:
1. 基本概念
计算属性是根据某些数据(例如
data
)计算得到的,只有当相关依赖的数据发生变化时,计算属性才会重新计算,这比在模板中直接写复杂的逻辑要高效得多。
示例:
<template><div><p>{{ fullName }}</p></div>
</template><script>
export default {data() {return {firstName: 'John',lastName: 'Doe'};},computed: {fullName() {return this.firstName + ' ' + this.lastName;}}
}
</script>
在上面的示例中,
fullName
是一个计算属性,它根据firstName
和lastName
计算出完整的名字。如果
firstName
或lastName
发生变化,fullName
会自动更新。
2. 计算属性 vs 方法
计算属性
- 计算属性是基于依赖的 缓存的。当依赖的数据没有变化时,计算属性不会重新计算,而是直接返回缓存的结果。
- 更适合进行一些依赖数据计算,不需要每次重新执行。
方法
- 方法会在每次渲染时 执行,即使依赖的数据没有变化,也会重新执行。
- 适用于那些不需要缓存的动态值。
示例:
<template><div><!-- 计算属性 --><p>{{ reversedMessage }}</p><!-- 方法 --><p>{{ reverseMessage() }}</p></div>
</template><script>
export default {data() {return {message: 'Hello Vue!'};},computed: {reversedMessage() {return this.message.split('').reverse().join('');}},methods: {reverseMessage() {return this.message.split('').reverse().join('');}}
}
</script>
reversedMessage
是一个计算属性,它只在message
发生变化时重新计算。reverseMessage
是一个方法,它每次调用时都会执行。
3. 计算属性的 getter 和 setter
计算属性默认只包含 getter,即计算属性的值。但是你也可以给计算属性添加 setter,来响应计算属性值的变化。
只读计算属性(getter)
computed: {fullName() {return this.firstName + ' ' + this.lastName;}
}
带有 setter 的计算属性
computed: {fullName: {get() {return this.firstName + ' ' + this.lastName;},set(newValue) {const names = newValue.split(' ');this.firstName = names[0];this.lastName = names[1];}}
}
在这个例子中,
fullName
是一个计算属性,它的 getter 用来返回firstName
和lastName
的组合,而 setter 允许你通过设置fullName
来更新firstName
和lastName
。
4. 计算属性的懒计算特性
计算属性只有在其依赖的响应式数据发生变化时才会重新计算。当多次访问计算属性时,它会使用缓存的值,直到依赖的数据发生变化。
示例:
<template><div><p>{{ reversedMessage }}</p><button @click="changeMessage">改变消息</button></div>
</template><script>
export default {data() {return {message: 'Hello Vue!'};},computed: {reversedMessage() {console.log('计算 reversedMessage');return this.message.split('').reverse().join('');}},methods: {changeMessage() {this.message = 'Hello World!';}}
}
</script>
在这个例子中,
reversedMessage
会在第一次访问时计算,但如果你连续访问它,直到message
发生变化时,计算属性才会重新计算。
5. 计算属性的性能优化
由于计算属性具有缓存特性,Vue
会自动管理计算属性的依赖关系,避免了不必要的重复计算,因此通常它们比方法更高效。当多个地方依赖同一数据时,计算属性会减少计算量。
6. 使用计算属性的场景
- 数据转换:例如将日期格式化为特定格式,或将列表中的数据进行筛选和排序。
- 处理复杂的表达式:如果在模板中需要多次使用一个复杂表达式,可以通过计算属性来封装它,避免重复计算。
- 动态类名/样式:基于数据动态返回类名或样式。
示例:
<template><div :class="buttonClass">按钮</div>
</template><script>
export default {data() {return {isActive: true};},computed: {buttonClass() {return {active: this.isActive,'btn-primary': !this.isActive};}}
}
</script>
在这个例子中,
buttonClass
计算属性根据isActive
的值动态返回不同的类名。
总结:
- 计算属性是 Vue 中强大而高效的功能,用于根据现有数据计算派生数据。
- 它有缓存特性,只有在依赖的数据变化时才重新计算。
- 计算属性的使用场景非常广泛,尤其适合处理复杂的逻辑并优化性能。
12.监视(侦听)属性
1. 监视属性watch:
1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
2.监视的属性必须存在,才能进行监视!!
3.监视的两种写法: (1).new Vue时传入watch配置 (2).通过vm.$watch监视
const vm = new Vue({el:'#root',data:{isHot:true,},computed:{info(){return this.isHot ? '炎热' : '凉爽'}},methods: {changeWeather(){this.isHot = !this.isHot}},watch:{isHot:{// immediate:true, //初始化时让handler调用一下//handler什么时候调用?当isHot发生改变时。handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}}} })// vm.$watch('isHot',{// immediate:true, //初始化时让handler调用一下// //handler什么时候调用?当isHot发生改变时。// handler(newValue,oldValue){// console.log('isHot被修改了',newValue,oldValue)// }// })
2.深度监视
深度监视:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。
const vm = new Vue({el:'#root',data:{isHot:true,numbers:{a:1,b:1,c:{d:{e:100}}}},computed:{info(){return this.isHot ? '炎热' : '凉爽'}},methods: {changeWeather(){this.isHot = !this.isHot}},watch:{isHot:{// immediate:true, //初始化时让handler调用一下//handler什么时候调用?当isHot发生改变时。handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}},//监视多级结构中某个属性的变化/* 'numbers.a':{handler(){console.log('a被改变了')}}*///监视多级结构中所有属性的变化numbers:{deep:true,handler(){console.log('numbers改变了')}}}})
3.监听属性的简写
watch:{//正常写法isHot:{// immediate:true, //初始化时让handler调用一下// deep:true,//深度监视handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}}, *///简写isHot(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue,this)} }})//正常写法vm.$watch('isHot',{immediate:true, //初始化时让handler调用一下deep:true,//深度监视handler(newValue,oldValue){console.log('isHot被修改了',newValue,oldValue)}}) //简写vm.$watch('isHot',(newValue,oldValue)=>{console.log('isHot被修改了',newValue,oldValue,this)})
4.监听属性和计算属性的对比
computed和watch之间的区别:
-
1.computed能完成的功能,watch都可以完成。
-
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则: -
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
-
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
总结: -
计算属性:适合计算并缓存值,只有在相关数据发生变化时才重新计算。
-
监听属性:适合响应数据变化并执行副作用操作,如异步请求或其他复杂操作。
13.绑定样式
1.绑定样式:
- class样式
- 写法:class=“xxx” xxx可以是字符串、对象、数组。
- 字符串写法适用于:类名不确定,要动态获取。
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
- 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 --><div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/><!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 --><div class="basic" :class="classArr">{{name}}</div> <br/><br/><!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 --><div class="basic" :class="classObj">{{name}}</div> <br/><br/>
- style样式
- :style="{fontSize: xxx}"其中xxx是动态值。
- :style="[a,b]"其中a、b是样式对象。
<!-- 绑定style样式--对象写法 --><div class="basic" :style="styleObj">{{name}}</div> <br/><br/><!-- 绑定style样式--数组写法 --><div class="basic" :style="styleArr">{{name}}</div>
Vue模块代码
const vm = new Vue({el:'#root',data:{name:'尚硅谷',mood:'normal',classArr:['atguigu1','atguigu2','atguigu3'],classObj:{atguigu1:false,atguigu2:false,},styleObj:{fontSize: '40px',color:'red',},styleObj2:{backgroundColor:'orange'},styleArr:[{fontSize: '40px',color:'blue',},{backgroundColor:'gray'}]},methods: {changeMood(){const arr = ['happy','sad','normal']const index = Math.floor(Math.random()*3)this.mood = arr[index]}},})
14. 条件渲染
条件渲染:
- v-if
- (1).v-if=“表达式”
- (2).v-else-if=“表达式”
- (3).v-else=“表达式”
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
- v-show
写法:v-show=“表达式”
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
- 备注:
使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
通过template和v-if 的配合使用,实现了在不改变原来盒子的样式布局的前提下,进行整体化的v-if渲染,
特别的:
在使用这个的时候,v-show是不能实现整体的一个渲染效果的
<!-- v-if与template的配合使用 --><template v-if="n === 1"><h2>你好</h2><h2>尚硅谷</h2><h2>北京</h2></template>
在点击一次按钮后,控制台上面显示的效果就是没有显示最外层的盒子
15.列表渲染
v-for指令:
- 1.用于展示列表数据
- 2.语法:v-for=“(item, index) in xxx” :key=“yyy”
- 3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
具体事例
<div id="root"><!-- 遍历数组 --><h2>人员列表(遍历数组)</h2><ul><li v-for="(p,index) of persons" :key="index">{{p.name}}-{{p.age}}</li></ul><!-- 遍历对象 --><h2>汽车信息(遍历对象)</h2><ul><li v-for="(value,k) of car" :key="k">{{k}}-{{value}}</li></ul><!-- 遍历字符串 --><h2>测试遍历字符串(用得少)</h2><ul><li v-for="(char,index) of str" :key="index">{{char}}-{{index}}</li></ul><!-- 遍历指定次数 --><h2>测试遍历指定次数(用得少)</h2><ul><li v-for="(number,index) of 5" :key="index">{{index}}-{{number}}</li></ul></div>
16.key的原理
面试题:react、vue中的key有什么作用?(key的内部原理)
- 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下: - 对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。(2).旧虚拟DOM中未找到与新虚拟DOM相同的key创建新的真实DOM,随后渲染到到页面。
-
用index作为key可能会引发的问题:
-
若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。 -
如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
-
-
开发中如何选择key?:
1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
chatgpt实现补充内容:
🧩 一、key
是什么?
在 Vue 中,当你使用 v-for
渲染一个列表时,每个列表项都应该有一个唯一的标识符,这个标识符就是 key
。
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
key
的作用是:
帮助 Vue 在虚拟 DOM 的 diff 算法 中更高效、准确地识别每个节点的身份。
⚙️ 二、没有 key
会发生什么?
当你用 v-for
渲染列表时,如果没有指定 key
,Vue 会采用一种“就地复用(in-place patching)”的策略:
<li v-for="(item, index) in list">{{ item }}</li>
例如:
data() {return {list: ['A', 'B', 'C']}
}
如果后来你把第一个元素删掉:
this.list = ['B', 'C']
Vue 会:
- 发现长度变短了,
- 于是直接复用原来的 DOM 元素,
- 只更新内容。
也就是说:
- 第一个
<li>
原本是A
,变成了B
; - 第二个
<li>
原本是B
,变成了C
。
🟡 问题:如果这些 <li>
内有输入框、动画、组件状态等,就会错位、混乱。
🧠 三、加上 key
后会发生什么?
如果我们写成:
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
Vue 在对比虚拟 DOM 时,会根据 key
判断节点是否是“同一个”。
Vue diff 的核心逻辑:
- 当新旧节点的
key
相同,Vue 认为它是同一个节点,只更新内容; - 当
key
不同,Vue 认为是新节点,会销毁旧节点并创建新节点。
🟢 结果:
Vue 可以准确判断哪个节点被删除、哪个节点被添加,避免错误复用,性能和一致性都更好。
🔍 四、key
的底层原理(虚拟 DOM diff)
Vue 渲染时会生成虚拟 DOM 树(Virtual DOM Tree)。
当数据更新时,Vue 会执行 diff 算法 来比较“新旧两棵虚拟树”,从而决定最小的 DOM 更新。
Diff 过程简化:
oldVNode: [ {key: 1}, {key: 2}, {key: 3} ]
newVNode: [ {key: 2}, {key: 3}, {key: 1} ]
Vue 会通过 key 快速定位每个节点的位置变化,而不是“逐个对比内容”。
- 如果没有
key
,Vue 就只能按顺序挨个对比; - 有了
key
,Vue 就能知道哪个节点该复用,哪个该移动。
✅ 有 key
→ Diff 更精准、更高效
❌ 没 key
→ Diff 可能导致错位或状态混乱
🧩 五、key
的使用规则
✅ 建议:
- 使用 唯一且稳定的标识符,如
id
。 - 如果没有唯一 id,可使用索引,但要慎重(见下方)。
⚠️ 不推荐:
<li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
因为当数组发生插入、删除时,索引会重新排列,导致组件复用错误。
比如输入框内容错位、动画错误、复选框状态丢失等。
💡 六、实际案例说明
❌ 错误写法(用索引作 key)
<div v-for="(todo, index) in todos" :key="index"><input v-model="todo.text">
</div>
如果你在中间插入一个新 todo,输入框的内容会错位。
✅ 正确写法(用唯一 ID)
<div v-for="todo in todos" :key="todo.id"><input v-model="todo.text">
</div>
此时 Vue 会根据每个 id
保证输入框状态和数据一一对应,不会错乱。
🧱 七、总结对比表
项目 | 有 key | 无 key |
---|---|---|
Diff 对比方式 | 根据 key 精确比对 | 按顺序复用节点 |
性能 | 更高效(可复用不变节点) | 低效(可能多余更新) |
节点状态 | 不会错乱 | 可能错位、状态丢失 |
场景 | 推荐所有 v-for 都加 key | 仅用于简单、静态列表 |
🧩 八、扩展知识点
-
key
不仅仅在 v-for 中使用
在v-if
、transition
、keep-alive
等中也会用到key
来标识唯一节点。<div v-if="show" key="A">A组件</div> <div v-else key="B">B组件</div>
👉 Vue 通过
key
区分这两个是不同的元素,否则可能复用 DOM 导致渲染问题。 -
key
必须是唯一的且稳定的- 不能重复;
- 不应该随着渲染变化;
- 通常绑定唯一 id 或唯一字符串。
✅ 总结一句话:
key
是 Vue 识别每个节点身份的唯一标识,它能让虚拟 DOM diff 更高效、更准确,避免组件状态错乱。
原则:所有 v-for 都应加唯一 key。
17.列表的过滤
1.watch实现
<div id="root"><h2>人员列表</h2><input type="text" placeholder="请输入名字" v-model="keyWord"><ul><li v-for="(p,index) of filPerons" :key="index">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div>//用watch实现//#region new Vue({el:'#root',data:{keyWord:'',persons:[{id:'001',name:'马冬梅',age:19,sex:'女'},{id:'002',name:'周冬雨',age:20,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}],filPerons:[]},watch:{keyWord:{immediate:true,handler(val){this.filPerons = this.persons.filter((p)=>{return p.name.indexOf(val) !== -1})}}}}) //#endregion
2.computed实现
//用computed实现new Vue({el:'#root',data:{keyWord:'',persons:[{id:'001',name:'马冬梅',age:19,sex:'女'},{id:'002',name:'周冬雨',age:20,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}]},computed:{filPerons(){return this.persons.filter((p)=>{return p.name.indexOf(this.keyWord) !== -1})}}})
18.列表的排序
<div id="root"><h2>人员列表</h2><input type="text" placeholder="请输入名字" v-model="keyWord"><button @click="sortType = 2">年龄升序</button><button @click="sortType = 1">年龄降序</button><button @click="sortType = 0">原顺序</button><ul><li v-for="(p,index) of filePersons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}<input type="text"></li></ul></div><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el: '#root',data: {keyWord: '',sortType: 0, //0原顺序 1降序 2升序persons: [{ id: '001', name: '马冬梅', age: 30, sex: '女' },{ id: '002', name: '周冬雨', age: 31, sex: '女' },{ id: '003', name: '周杰伦', age: 18, sex: '男' },{ id: '004', name: '温兆伦', age: 19, sex: '男' }]},computed: {filePersons() {const arr = this.persons.filter((p) => {return p.name.indexOf(this.keyWord) !== -1})if (this.sortType) {arr.sort((p1, p2) => {return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age})}return arr}}})</script>
🧩19.监视数据的原理
Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
2. 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或 vm.$set(target,propertyName/index,value)
3. 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
5.具体代码实现
<div id="root"><h1>学生信息</h1><button @click="student.age++">年龄+1岁</button> <br /><button @click="addSex">添加性别属性,默认值:男</button> <br /><button @click="student.sex = '未知' ">修改性别</button> <br /><button @click="addFriend">在列表首位添加一个朋友</button> <br /><button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br /><button @click="addHobby">添加一个爱好</button> <br /><button @click="updateHobby">修改第一个爱好为:开车</button> <br /><button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br /><h3>姓名:{{student.name}}</h3><h3>年龄:{{student.age}}</h3><h3 v-if="student.sex">性别:{{student.sex}}</h3><h3>爱好:</h3><ul><li v-for="(h,index) in student.hobby" :key="index">{{h}}</li></ul><h3>朋友们:</h3><ul><li v-for="(f,index) in student.friends" :key="index">{{f.name}}--{{f.age}}</li></ul></div>
</body><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#root',data: {student: {name: 'tom',age: 18,hobby: ['抽烟', '喝酒', '烫头'],friends: [{ name: 'jerry', age: 35 },{ name: 'tony', age: 36 }]}},methods: {addSex() {Vue.set(this.student, 'sex', '男')},addFriend() {this.student.friends.unshift({ name: 'jack', age: 60 })},updateFirstFriendName() {this.student.friends[0].name = '李思'this.student.friends[0].age = 25},addHobby() {this.student.hobby.push('学习')},updateHobby() {// this.student.hobby.splice(0,1,'开车')// Vue.set(this.student.hobby,0,'开车')this.$set(this.student.hobby, 0, '开车')},removeSmoke() {this.student.hobby = this.student.hobby.filter((h) => {return h !== '抽烟'})}}})
</script>
注意:
🍎所有不是由Vue所控制的回调,尽可能的使用箭头函数,原因是因为箭头函数没有this的指向,回向上寻找,直到找到vm
6.chatgpt总结
Vue2监视数据变化的原理是数据劫持 + 依赖收集 + 派发更新,核心依靠Object.defineProperty()
实现。
一、数据劫持(Observer)
Vue在初始化data
时,会对所有属性进行递归遍历,用Object.defineProperty
给每个属性设置getter
和setter
:
Object.defineProperty(obj, key, {get() {// 依赖收集return value},set(newVal) {// 通知视图更新value = newValdep.notify()}
})
这样Vue就能拦截对数据的访问和修改,实现自动监听。
二、依赖收集(Dep & Watcher)
- 每个被监听的数据属性,都有一个对应的
Dep
对象(依赖管理器)。 - 每个使用到这个属性的组件或模板表达式,会创建一个
Watcher
对象。 - 当数据被读取时(触发
getter
),Watcher
会被添加到对应的Dep
中。
结果:
Vue知道“哪个数据被哪些地方用到”。
三、派发更新
当数据被修改时(触发setter
):
- 调用
dep.notify()
- 所有依赖该数据的
Watcher
收到通知 - 每个
Watcher
执行更新函数 → 重新计算虚拟DOM → 触发视图重新渲染
四、数组的特殊处理
Object.defineProperty
无法监听数组索引变化。
Vue2通过重写数组7个变异方法来实现响应式:
push
、pop
、shift
、unshift
、splice
、sort
、reverse
。
当这些方法被调用时,会手动触发dep.notify()
。
五、原理流程图(逻辑)
data初始化 → defineProperty劫持属性↓
模板访问数据 → getter收集依赖↓
数据变化 → setter触发 → dep.notify()↓
Watcher更新 → 重新渲染视图
20.收集表单数据
1.收集表单数据:
若:,则v-model收集的是value值,用户输入的就是value值。 若:,则v-model收集的是value值,且要给标签配置value值。 若:
2. 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
3. 配置input的value属性:
- (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
- (2)v-model的初始值是数组,那么收集的的就是value组成的数组
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
<div id="root"><form @submit.prevent="demo">账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/> //去掉前面和后面的空格密码:<input type="password" v-model="userInfo.password"> <br/><br/>年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>性别:男<input type="radio" name="sex" v-model="userInfo.sex" value="male">女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>爱好:学习<input type="checkbox" v-model="userInfo.hobby" value="learn">打游戏<input type="checkbox" v-model="userInfo.hobby" value="play_Games">吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat_food"><br/><br/>所属校区<select v-model="userInfo.city"><option value="">请选择校区</option><option value="beijing">北京</option><option value="shanghai">上海</option><option value="shenzhen">深圳</option><option value="wuhan">武汉</option></select><br/><br/>其他信息:<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>//<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a><button>提交</button></form></div></body><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el:'#root',data:{userInfo:{account:'',password:'',age:18,sex:'female',hobby:[],city:'beijing',other:'',agree:''}},methods: {demo(){alert('表单已经提交,可以通过控制台进行查看你提交的数据')console.log(JSON.stringify(this.userInfo))}}})</script>
21.过滤器
1.定义:
对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
2.语法:
- 1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
- 2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”
3.备注:
- 1.过滤器也可以接收额外参数、多个过滤器也可以串联
- 2.并没有改变原本的数据, 是产生新的对应的数据
<div id="root"><h2>显示格式化后的时间</h2><!-- 计算属性实现 --><h3>现在是:{{fmtTime}}</h3><!-- methods实现 --><h3>现在是:{{getFmtTime()}}</h3><!-- 过滤器实现 --><h3>现在是:{{time | timeFormater}}</h3><!-- 过滤器实现(传参) --><h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3><h3 :x="msg | mySlice">尚硅谷</h3></div><div id="root2"><h2>{{msg | mySlice}}</h2></div></body><script type="text/javascript">Vue.config.productionTip = false//全局过滤器Vue.filter('mySlice',function(value){return value.slice(0,5)})new Vue({el:'#root',data:{time:Date.now(), //时间戳msg:'你好,尚硅谷'},computed: {fmtTime(){return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')}},methods: {getFmtTime(){return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')}},//局部过滤器filters:{timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){// console.log('@',value)return dayjs(value).format(str)}}})new Vue({el:'#root2',data:{msg:'hello,atguigu!'}})</script>
4.chatgpt总结:
Vue中收集表单数据的核心知识点如下:
一、核心机制:v-model
双向绑定
v-model
是 Vue 用来实现表单数据双向绑定的指令。
- 输入框输入内容 → 自动更新数据
- 数据改变 → 自动更新表单显示
示例:
<input v-model="username" placeholder="请输入用户名" />
等价于:
<input :value="username" @input="username = $event.target.value" />
二、常见表单控件绑定
- 文本输入框
<input v-model="text" type="text" />
- 多行文本
<textarea v-model="message"></textarea>
- 单选框
<input type="radio" v-model="gender" value="男">男
<input type="radio" v-model="gender" value="女">女
数据:gender
为字符串 "男"
或 "女"
。
- 复选框
-
绑定布尔值:
<input type="checkbox" v-model="checked" />
-
绑定数组:
<input type="checkbox" value="HTML" v-model="skills" />HTML <input type="checkbox" value="CSS" v-model="skills" />CSS <input type="checkbox" value="JS" v-model="skills" />JS
数据:
skills
是数组,选中的值会自动添加或移除。
- 下拉框
<select v-model="city"><option value="bj">北京</option><option value="sh">上海</option>
</select>
三、修饰符
-
.lazy
:在失去焦点后更新数据<input v-model.lazy="msg" />
-
.number
:自动将输入转为数字<input v-model.number="age" />
-
.trim
:自动去掉首尾空格<input v-model.trim="name" />
四、数据收集流程
用户输入 → v-model触发input事件 → 更新data数据 → 组件或DOM自动刷新显示
22.内置指令
总结:
我们学过的指令:
- v-bind : 单向绑定解析表达式, 可简写为 :xxx
- v-model : 双向数据绑定
- v-for : 遍历数组/对象/字符串
- v-on : 绑定事件监听, 可简写为@
- v-if : 条件渲染(动态控制节点是否存存在)
- v-else : 条件渲染(动态控制节点是否存存在)
- v-show : 条件渲染 (动态控制节点是否展示)
1.v-text
1.作用:
向其所在的节点中渲染文本内容。
2.与插值语法的区别:
v-text会替换掉节点中的内容,{{xx}}则不会。
<div id="root"><div>你好,{{name}}</div><div v-text="name"></div><div v-text="str"></div></div>
2.v-html
1. 作用:
向指定节点中渲染包含html结构的内容。
2. 与插值语法的区别:
- (1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
- (2).v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
- (1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
- (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
<div id="root"><div>你好,{{name}}</div><div v-html="str"></div><div v-html="str2"></div></div>
3.v-cloak(没有值)
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
4.v-once
- v-once所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
在语句中加入v-once后,会使第一句在初次渲染以后,转化为静态内容
5.v-pre
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
其中,“Vue其实很简单就是所谓的::没有使用指令语法、没有使用插值语法的节点”’
chatgpt总结
分类 | 指令 | 功能 |
---|---|---|
数据绑定 | v-bind | 绑定属性 |
事件绑定 | v-on | 绑定事件 |
条件控制 | v-if / v-show | 控制渲染与显示 |
列表渲染 | v-for | 遍历数据 |
表单输入 | v-model | 双向绑定 |
性能优化 | v-once / v-pre | 控制编译与更新 |
DOM操作 | v-text / v-html | 输出文本或HTML |
对比
1. v-if和v-show
对比点 | v-if | v-show |
---|---|---|
本质 | 控制DOM创建与销毁 | 控制CSS display 显隐 |
渲染逻辑 | 条件为 false 时元素不会存在于DOM中 | 元素始终在DOM中,只是隐藏 |
性能特点 | 切换频繁时性能差(反复创建/销毁) | 初次渲染慢,切换快 |
使用场景 | 不常切换的条件显示 | 频繁切换的显示状态 |
2.v-text和v-html
对比点 | v-html | v-text |
---|---|---|
作用 | 渲染HTML结构 | 输出纯文本 |
风险 | 存在XSS风险 | 安全 |
用途 | 后端返回HTML字符串 | 普通文字内容 |
3.v-bind和v-model
对比点 | v-bind | v-model |
---|---|---|
方向 | 单向绑定:数据 → 视图 | 双向绑定:数据 ↔ 视图 |
触发方式 | 仅依赖数据更新 | 同时监听 input 事件 |
常用场景 | 动态属性值 | 表单元素输入同步 |
4.v-if和v-show
v-for vs v-if(同时使用)
Vue2 中不推荐在同一元素上同时使用它们。
v-for 优先级更高,v-if 会对每次循环项都执行判断。
若要过滤列表,应使用计算属性代替。
示例:
<li v-for="item in filteredList" :key="item.id">{{ item.name }}</li>