vue2_9、列表渲染、数据监视原理
1列表渲染
1.1、基本列表
v-for指令
-
用于展示列表数据
-
语法:
<li v-for="(item,index) in items " :key="index">
,这里 key 可以是 index ,更好是遍历的对象的唯一标识。 -
可遍历:数组、对象、字符串(用的少)、指定次数(用的少)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>初始vue</title>
<!-- 引入vue-->
<script src="/vueBaseJs/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 遍历数组-->
<h2>人员列表</h2>
<ul>
<li v-for="(p,index) in personList" :key="p.id">
{{p.name}} _ {{p.age}}
</li>
</ul>
<!-- 遍历对象-->
<h2>车配置列表</h2>
<ul>
<li v-for="(val,k) of car" ::key="k">
{{k}} : {{val}}
</li>
</ul>
<!-- 遍历字符串 -->
<!-- <h2>遍历字符串</h2>
<ul>
<li v-for="(char,index) of str" ::key="index">
{{index}} _ {{char}}
</li>
</ul> -->
<!-- 遍历次数 -->
<!-- <h2>遍历次数</h2>
<ul>
<li v-for="(number,index) in 5 " ::key="index">
{{number}} _ {{index}}
</li>
</ul> -->
</div>
<script>
Vue.config.productionTip=false;//阻止vue启动时生成生产提示
const vm=new Vue({
el:"#root",
data:function(){
return {
personList:[
{id:001,name:"张三",age:18},
{id:002,name:"李四",age:19},
{id:003,name:"王五",age:20}
],
car:{
name:"奥迪rs7",
price:"150w",
color:"黑色"
},
str:"Hello World!"
}
},
});
</script>
</body>
</html>
结果:
1.2、v-for的key的原理
index作为key
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>初始vue</title>
<!-- 引入vue-->
<script src="/vueBaseJs/vue.js"></script>
</head>
<body>
<div id="root">
<button @click.once="add">添加</button>
<!-- 遍历数组-->
<h2>人员列表</h2>
<ul>
<li v-for="(p,index) in personList" :key="index">
{{p.name}} _ {{p.age}}
<input type="text" >
</li>
</ul>
</div>
<script>
Vue.config.productionTip=false;//阻止vue启动时生成生产提示
const vm=new Vue({
el:"#root",
data:function(){
return {
personList:[
{id:001,name:"张三",age:18},
{id:002,name:"李四",age:19},
{id:003,name:"王五",age:20}
],
}
},
methods:{
add(){
const p={id:004,name:"老刘",age:40};
this.personList.unshift(p);
}
}
});
</script>
</body>
</html>
点击添加前:
点击添加后:
为啥会这样:
因为用index作为key:
如果对数据的顺序进行了破坏的操作。就会有下面的问题,而且出现这样问题效率会很低,因为复用的东西变少了。
原因步骤①、如下图:点击添加后,在进行diff比较时候,发现第一个input文本框没有变化,在虚拟dom里面的key为0的节点里还是一个input元素没有变成其它元素,就可以复用,所以就不会重新生成新的元素进行替换。
②如下:后面的key的input都复用了真实dom上的,没有重新在虚拟dom上生成新的。
③王五重新生成是因为旧的虚拟dom里没有key=3的元素
p.id作为键:
如图,修改key
原理:
总结key的内部原理
面试题:react vue中的key有什么作用? ( key的内部原理)
1 .虚拟DOM
中key
的作用:key
是虚拟DOM
中对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM ,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,对比规则如下
2 .对比规则
a.旧虚拟DOM中找到了与新虚拟DOM相同的key
- ①.若虚拟DOM中内容没变,直接使用之前的真实DOM节点。
- ②若虚拟DOM中内容变了,则生成新的真实DOM节点 ,随后替换掉页面中之前的真实DOM中对应的有变化的节点元素。(不是替换掉整个dom文档,而是替换掉里面修改的节点元素)
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 创建新的真实DOM ,随后渲染到到页面
3 .用index作为key可能会引发的问题
a.若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低。
b.若结构中还包含输入类的DOM(如:输入框) :会产生错误DOM更新 ==> 界面有问题。
4 .开发中如何选择key ?
a.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
b,如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用index作为key是没有问题的。
1.3、列表过滤
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>初始vue</title>
<!-- 引入vue-->
<script src="/vueBaseJs/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 遍历数组-->
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord" >
<ul>
<li v-for="(p,index) in personListTemp " :key="p.id">
{{p.name}} - {{p.age}} - {{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip=false;//阻止vue启动时生成生产提示
////用watch监视属性实现
// const vm=new Vue({
// el:"#root",
// data:function(){
// return {
// personList:[
// {id:001,name:"马冬梅",age:18,sex:"女"},
// {id:002,name:"周冬雨",age:19,sex:"女"},
// {id:003,name:"周杰伦",age:20,sex:"男"},
// {id:004,name:"温兆伦",age:21,sex:"男"}
// ],
// keyWord:"",
// personListTemp:[]
// }
// },
// watch:{
// keyWord:{
// immediate:true,
// handler:function(newVal,oldVal){
// this.personListTemp=this.personList.filter(m=>m.name.indexOf(newVal)!=-1);
// }
// }
// }
// });
//计算属性实现
const vm=new Vue({
el:"#root",
data:function(){
return {
personList:[
{id:001,name:"马冬梅",age:18,sex:"女"},
{id:002,name:"周冬雨",age:19,sex:"女"},
{id:003,name:"周杰伦",age:20,sex:"男"},
{id:004,name:"温兆伦",age:21,sex:"男"}
],
keyWord:"",
}
},
computed:{
personListTemp:{
get(){
return this.personList.filter(m=>m.name.indexOf(this.keyWord)!=-1);
}
},
}
});
</script>
</body>
</html>
结果:
1.4、列表排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>初始vue</title>
<!-- 引入vue-->
<script src="/vueBaseJs/vue.js"></script>
</head>
<body>
<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) in personListTemp " :key="p.id">
{{p.name}} - {{p.age}} - {{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip=false;//阻止vue启动时生成生产提示
//计算属性实现
const vm=new Vue({
el:"#root",
data:function(){
return {
personList:[
{id:001,name:"马冬梅",age:18,sex:"女"},
{id:002,name:"周冬雨",age:20,sex:"女"},
{id:003,name:"周杰伦",age:17,sex:"男"},
{id:004,name:"温兆伦",age:19,sex:"男"}
],
keyWord:"",
sortType:0,//0原顺序、1降序、2升序
}
},
methods:{
},
computed:{
personListTemp:{
get(){
const arr=this.personList.filter(m=>m.name.indexOf(this.keyWord)!=-1);
if(this.sortType!==0){
this.sortType==1? arr.sort((a,b)=> b.age-a.age): arr.sort((a,b)=> -(b.age-a.age));
}
return arr;
}
},
}
});
</script>
</body>
</html>
1.5、Vue数据监视
更新时的问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>初始vue</title>
<!-- 引入vue-->
<script src="/vueBaseJs/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 遍历数组-->
<h2>人员列表</h2>
<button @click="updataMa">更新 马冬梅的信息</button>
<ul>
<li v-for="(p,index) in personList " :key="p.id">
{{p.name}} - {{p.age}} - {{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip=false;//阻止vue启动时生成生产提示
//vue监测数据改变的原理:vue底层自己会有监视,监视data里的数据是否改变,改变了就去更新用到改变的数据的地方。
//计算属性实现
const vm=new Vue({
el:"#root",
data:function(){
return {
personList:[
{id:001,name:"马冬梅",age:18,sex:"女"},
{id:002,name:"周冬雨",age:20,sex:"女"},
{id:003,name:"周杰伦",age:17,sex:"男"},
{id:004,name:"温兆伦",age:19,sex:"男"}
],
keyWord:"",
sortType:0,//0原顺序、1降序、2升序
}
},
methods:{
updataMa(){
// this.personList[0].name="马宝国";//有效
// this.personList[0].age=60;//有效
// this.personList[0].sex="男"//有效
//有效是因为student.personList[0]是对象,对象上的name属性vue有给它配置set属性作为数据代理。如果student.personList[0]不是对象,也就是数组里存放的是字符串,那么就无法监视
this.personList[0]={id:001,name:"马宝国",age:60,sex:"男"};//没有效果,因为这个赋值的方式不是响应式的,主要是数组里的下标vue没有给它配置set数据代理
}
}
});
</script>
</body>
</html>
如下图:更新时候页面上并没有更新:
解决方法:
更改如下代码即可,因为vue对能修改数组本身的方法才进行数据监视,
简单模拟vue中的监测对象变化的原理
<script type="text/javascript">
//模拟创建Vue时配置对象里面的data数据
let data={
name:"南职院",
address:"南充"
}
//创建监视的构造函数
function Observer(obj){
//将对象中的属性汇总成一个属性数组
const keyArr=Object.keys(obj);
keyArr.forEach((key,index)=>{
//这里this是Observer的实例,因为上面是箭头函数,如果是funtion形式的话,this就变成window了。
Object.defineProperty(this,key,{
get(){
return obj[key];
},
set(value){
console.log(key+"被改变了,我要去解析模板生成虚拟dom,进行比较.....开始忙了");
obj[key]=value;
}
});
});
}
//创建一个监视的实例对象,用于监视data中的属性变化。在vue中相当于将data格式加工为vm实例里的_data格式
const obs=new Observer(data);
//模拟创建好vm实例
const vm={}
//将实例的_data和data都赋值为加工后的样子
vm._data=data=obs;
console.log(vm);
</script>
Vue.set();和vm.$set()
非响应式添加属性
Vue.set():响应式添加属性
添加的属性就和vue生成虚拟dom时候加工data里的数据一样,都有get和set监听。
vm.$set()
方法和Vue.set()
方法一致,只不过一个是构造函数身上的,一个是实例身上的。
vm.$set(vm.student,"sex","女");
Vue监视数据的原理:
-
vue会监视data中所有层次的数据。
-
如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value)
或
vm.$set(target,propertyName/index,value)
-
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。 -
在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象(data)添加属性!!!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../vueBaseJs/vue.js"></script>
</head>
<body>
<div id="root">
<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="student.hobby.push('运动')">添加一个爱好</button> <br/>
<button @click="updateFirstHobby">修改第一个爱好为:开车</button> <br/>
<button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>
<br/>
<h2>学生信息</h2>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h4>爱好:</h4>
<ul>
<li v-for="(val,index) in student.hobby" ::key="index">
{{val}}
</li>
</ul>
<h4>朋友们:</h4>
<ul>
<li v-for="(v,index) in student.friends " :key="index">
{{v.name +":"+v.age}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip=false;//阻止vue启动时生成生产提示
const vm=new Vue({
el:"#root",
data:function(){
return{
student:{
name:"tom",
age:18,
friends:[
{name:"jeer",age:35},
{name:"maker",age:30}
],
hobby:["抽烟","喝酒","上网"],
},
}
},
methods:{
addSex(){
this.$set(this.student,"sex","男");
},
addFriend(){
this.student.friends.unshift({name:"queen",age:38});
},
updateFirstFriendName(){
this.student.friends[0].name='张三';
//student.friends[0]是对象,对象上的name属性vue有给它配置set属性作为数据代理。
},
updateFirstHobby(){
//this.student.hobby.splice(0,1,"开车");//方式1
this.$set(this.student.hobby,0,"开车");
},
removeSmoke(){
this.student.hobby=this.student.hobby.filter(m=> m!="抽烟");
}
}
});
</script>
</body>
</html>
数据劫持
就是将data里的数据转为vm里_data的格式,为每个属性添加getter和setter的操作就叫数据劫持。劫持就是修改属性被set发现,让后set执行操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构