本文主要是介绍29.Vue监测数据改变的原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
1.Vue数据更新却监测不到的问题
2.Vue监测数据改变的原理
2.1 Vue是如何监测对象中数据的改变的
2.2 简单模拟Vue监视属性的代码
2.3 Vue.set()方法的使用
2.4 Vue是如何监测数组中数据的改变的
3.总结
我们之前都用过好多Vue的配置项以及插值语法。比如我们在data配置项中配置一个name,然后在页面上再写个插值语法 {{name}} ,我们都知道,当我们把data中的name改掉以后,那么页面上插值语法中的name也会自动更新。这里的前因后果是因为,我们改了data中的数据被Vue监测到了,随后Vue才将插值语法中的name进行了改变。那么我们这里要研究的就是Vue是如何监测到我们改掉了data中的数据的,并且拿到我们改变的最新值的。
这个时候有人说了,这难道不是通过Vue的监视属性watch实现的吗?这里就要注意了,我们通过watch可以监测到某一个属性的变化这只是表象,Vue拿到这个watch之后具体得怎么写才能监视呢?那就又涉及到底层原理了。
所以其实是这么回事,Vue默认就有一个监视,这个监视是用来干嘛的?就是用来当我们改掉数据的时候,去帮我们更新页面上用到这个数据的地方。而Vue还给我们提供了一个watch配置项也叫监视,但这个watch是给程序员使用的。那么无论是watch,还是Vue默认的监视,它里面的原理都是一样的,他两底层用的是一套类似的逻辑。
1.Vue数据更新却监测不到的问题
Vue监测数据改变的原理我们必须得懂,要不然在未来的某一天,当我们写了一个我们认为很正常的一段代码,可是无论我们怎么改这个数据,Vue都监测不到。
接下来我们就暴露一下这个问题,就是看起来很正常,但是改完数据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>列表排序</title><!--引入Vue--><script type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><!-- 遍历列表 --><h2>人员列表</h2><button @click="updateMei">更新马冬梅的信息</button><ul><li v-for="(p,index) in persons" :key="p.id">{{p.name}} -- {{p.age}} -- {{p.sex}}</li></ul></div>
</body><script type="text/javascript">Vue.config.productionTip = false //阻止Vue在启动时生成生产提示const vm = new Vue({el:'#root',data:{persons:[{id:'001',name:'马冬梅',age:30,sex:'女'},{id:'002',name:'周冬雨',age:31,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}]},methods: {updateMei(){this.persons[0].name = '马老师'this.persons[0].age = 50this.persons[0].sex = '男'}},computed:{}})</script>
</html>
实现效果:
这时有人就说了,这不是更新的好好的嘛?也没啥问题呀。
不要着急,马上就有问题了,上面写的这种是正确的写法,我们是一个属性一个属性改的,接下来就是有问题的写法。
<!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>列表排序</title><!--引入Vue--><script type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><!-- 遍历列表 --><h2>人员列表</h2><button @click="updateMei">更新马冬梅的信息</button><ul><li v-for="(p,index) in persons" :key="p.id">{{p.name}} -- {{p.age}} -- {{p.sex}}</li></ul></div>
</body><script type="text/javascript">Vue.config.productionTip = false //阻止Vue在启动时生成生产提示const vm = new Vue({el:'#root',data:{persons:[{id:'001',name:'马冬梅',age:30,sex:'女'},{id:'002',name:'周冬雨',age:31,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}]},methods: {updateMei(){// this.persons[0].name = '马老师' //奏效// this.persons[0].age = 50 //奏效// this.persons[0].sex = '男' //奏效this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}}},computed:{}})</script>
</html>
实现效果:
我们可以看到,在这种写法下,无论怎么点击,数据都不会发生变化。
这个时候就有人懵了,那这到底是改了还是没改呢?
是这样的。从代码的层面上来说,我们的确是把数组中的某一项给彻底替换掉了。但是Vue并没有监测到。
这时有人就会问,那怎么才能证明这个数据已经被真正的替换掉了呢?Vue的开发者工具中都没有体现变化。这是因为Vue根本就没有监测到数据的变化,所以在Vue的开发者工具中自然也就不会体现这种变化。
为了说明数组中已经发生了变化,我们可以在控制台输出一下。
那么讲到这里我们就把我们想要暴露的问题给暴露出来了。
2.Vue监测数据改变的原理
为了把刚才我们暴露出来的问题说明白,我们就得知道Vue是如何监测数组中元素的改变的。但是我们要讲的话,最好先讲清楚Vue是如何监测对象中数据改变的,随后再引出来Vue是如何监测数组的。之后就可以把我们刚才暴露的问题给解答清楚了。
2.1 Vue是如何监测对象中数据的改变的
12.Vue中的数据代理_爱米酱的博客-CSDN博客上一节我们知道了什么是数据代理之后,那么我们这一节就来看看Vue中是如何应用数据代理的。https://liufr.blog.csdn.net/article/details/123044053我们在之前Vue的数据代理中讲到过,vm身上的属性都是来自于_data中,而_data中的数据都是来自于咱们配置的data,也就是vm._data = data。但是Vue在执行vm._data = data的时候已经是第二步了。它在第一步还做了一个操作那就是加工data,改变了data中的一些小东西,随后才将值给了_data。
那我们要怎么证明这件事呢?
如果我们没有第一步对data的加工步骤,直接执行了第二步的vm._data = data,那么就意味着,_data的值和我们配置的data应该是一摸一样的。
所以接下来我们就看看_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>Vue监测数据改变的原理</title><!--引入Vue--><script type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
</body><script type="text/javascript">Vue.config.productionTip = false //阻止Vue在启动时生成生产提示const vm = new Vue({el:'#root',data:{name:'北大',address:'北京'},methods: {},computed:{}})</script>
</html>
实现效果:
我们可以看到_data很明显和咱们当时配置项里写的data不一样。
_data做了一件事,就是把data中的每一组key-value都生成了getter,setter的写法,如下图:
这里就会有人问,加工一下data能起到什么作用呢? 答案就是加工完data就可以做响应式了。
什么是响应式呢?数据变了,页面也跟着变,这就叫响应式
当我们修改了_data中的name的时候,就会引起name所对应的setter的调用,而setter中又写了可以重新解析模板的调用。模板重新解析之后,就会生成新的虚拟DOM,然后就会有新旧DOM的对比,最后就更新了页面。这样就把一整套流程走通了。
也就是说Vue通过对data的加工,实现了针对对象中属性的监视。
2.2 简单模拟Vue监视属性的代码
其实很多人对这个_data都会有疑问,为什么不能直接通过data来实现属性监视,却需要通过_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>模拟一个数据监测</title><style></style>
</head>
<body></body><script type="text/javascript">let data = {name:'北京'}Object.defineProperty(data,'name',{get(){return data.name},set(val){data.name = val}})</script>
</html>
实现效果:
我们可以看到,当我们试图直接通过data来实现属性的监视的时候,无论是读取操作,还是修改操作都会报错,尽管他看起来与Vue实现的_data结构是一样的,都有属性名称及对应的getter和setter。
那么这个时候,我们再回过头来看看我们试图通过直接使用data实现属性监视的代码。
在代码中,只要有人读取data中的name,那么就会调用代码中的getter,而getter方法中又在读取data中的name,这样的话,就相当于形成了一个死循环。同理,只要有人修改data中的name,就会调用setter,而setter中又在修改data中的name,也是死循环。
所以_data的这种中转存在才是有意义的。并不能直接通过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>模拟一个数据监测</title><style></style>
</head>
<body></body><script type="text/javascript">let data = {name:'北京',address:'beijing'}//创建一个监视的实例对象,用于监视data中的属性变化const obs = new Observer(data);let vm = {}vm._data = data = obs//这个构造函数可以创建一个监视的实例对象function Observer(obj){//汇总对象中所有的属性形成一个数组const keys = Object.keys(obj)keys.forEach((k)=>{/*** 这里的this并不是指的data本身,* 而是这个函数将要生成的实例对象* 相当于通过另一个对象做了中转* 这样就不会出现死循环的情况* */Object.defineProperty(this,k,{get(){return obj[k]},set(val){console.log(`${k} 被改了,我要去解析模板,生成虚拟DOM...`)obj[k] = val}})})}</script>
</html>
实现效果:
2.3 Vue.set()方法的使用
虽然现在我们知道了Vue监测对象数据变化的原理,但是有一个Api我们还没学,那就是Vue.set(),这个Api特别适合在了解了对象监测之后去学。我们先把这个了解清楚,然后再去讲如何监测数组,然后再去讲1中我们暴露出来的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 type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><h2>名称:{{name}}</h2><h2>地址:{{address}}</h2><hr><h2>姓名:{{student.name}}</h2><h2>年龄:真实{{student.age.rAge}},对外:</h2><h2>朋友们</h2><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:{name:'北大',address:'北京',student:{name:'tom',age:{rAge:40,sAge:29},friends:[{name:'jerry',age:35},{name:'tony',age:36}]}}})</script>
</html>
实现效果:
呈现出来以后我们就会发现Vue做的真的很不错,有student就会有它对应的getter和setter。
展开student以后,可以看到在student中的age也有对应的getter和setter。
而展开age之后,里面的rAge和sAge仍然有对应的getter和setter,可以看到Vue的这种结构是层层递进的 ,虽然Vue这种方式做的很不错,但也不是那么完善,我们接下来看看。
如果我们想给这个学生添加一个性别,并且性别是男,我们应该怎么写?
这个时候就会有人说,那还不简单嘛?直接是data中加一个sex,然后用插值语法,写一个 {{student.sex}} 不就行了嘛?
那如果是这样的情况呢?页面上本来就有 {{student.sex}} 的设置,但是data中一开始并没有student.sex的属性,这个属性需要在之后的交互中动态的去添加。那该怎么办?
<!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 type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><h2>名称:{{name}}</h2><h2>地址:{{address}}</h2><hr><h2>姓名:{{student.name}}</h2><h2>性别:{{student.sex}}</h2><h2>年龄:真实{{student.age.rAge}},对外:</h2><h2>朋友们</h2><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:{name:'北大',address:'北京',student:{name:'tom',age:{rAge:40,sAge:29},friends:[{name:'jerry',age:35},{name:'tony',age:36}]}}})</script>
</html>
实现效果:
这里要注意student是存在的,但是student中不存在sex的属性,所以读取了一个对象中不存在的属性值,返回的就是undefined,是不会报错的。那为什么页面上没有显示undefined呢?这是因为Vue做了处理,如果是undefined值是不会显示到页面的。
那么接着说,如果我们一开始的data中没有student.sex的属性,以后想动态添加该怎么添?
这个时候就会有人说,那不是很简单嘛?直接通过student.sex = '男' 设置一下不行嘛?
真的有这么简单嘛?我们可以试一下。
我们可以看到,性别那一行,在添加完属性之后并没有任何变化,也没有出现我们能刚才添加的值。这是为什么呢?
我们在控制台输出看一下
我们可以看到,刚刚动态添加的sex值,并没有生成对应的getter和setter方法 ,那这样就可以解释为什么我们明明设置值了,但是页面却没有任何变化。
我们代码中的age,name都是一开始在new Vue实例的时候就写好的,而这些属性都会被Vue生成对应的getter和setter。其中最重要的就是setter,因为setter可以影响页面。所以我们后添加的sex,由于没有getter和setter,自然也就不是一个响应式的数据。
那么我们想要动态的去添加属性就没有办法了嘛?难道只能一开始就在data中配置好才行嘛?
也不是,Vue为这种情况,给我们提供了一个办法,它为我们提供了一个API,让我们可以实现动态添加的属性也可以有响应式的功能。它就是 Vue.set(target,key,val)
其中target是需要添加属性的目标对象,key是需要添加的属性名称,val是需要添加的属性值。
我们再用这个API试试
我们可以看到这次页面就有变化了,后添加的性别属性值也可以成功在页面显示了。
而且通过这个API添加的属性,也会有对应的getter和setter,就会是一个响应式的数据。
那么除了可以使用Vue.set()这个API,还有另一个API可以用,它和Vue.set()一模一样只是换了个名字,它就是 vm.$set(target,key,val)
下面我们再用这个API试一下
我们可以看到,这个API同样好用。
虽然Vue.set()及vm.$set()都很好用,但是它们也有局限性,接下来我们就演示一下这个局限。
我们刚刚是给data中的student添加的sex,接下来我们直接在data中添加一个属性leader试试。
<!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 type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><h2>名称:{{name}}</h2><h2>地址:{{address}}</h2><h2>校长:{{leader}}</h2><hr><h2>姓名:{{student.name}}</h2><h2>性别:{{student.sex}}</h2><h2>年龄:真实{{student.age.rAge}},对外:</h2><h2>朋友们</h2><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:{name:'北大',address:'北京',student:{name:'tom',age:{rAge:40,sAge:29},friends:[{name:'jerry',age:35},{name:'tony',age:36}]}}})</script>
</html>
实现效果:
注意,这里的报错是正常的,因为data中确实没有leader这个属性。
我们可以看到在给data动态添加属性的时候报错了,而且报错提示不允许给根节点添加动态属性。
也就是说Vue.set()及vm.$set() 只能给data中的某一个对象添加属性 而不能给data添加属性
2.4 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 type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><h2>名称:{{name}}</h2><h2>地址:{{address}}</h2><h2>校长:{{leader}}</h2><hr><h2>姓名:{{student.name}}</h2><h2>性别:{{student.sex}}</h2><h2>年龄:真实{{student.age.rAge}},对外:</h2><h2>爱好</h2><ul><li v-for="(h,index) in student.hobby" :key="index">{{h}}</li></ul><h2>朋友们</h2><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:{name:'北大',address:'北京',student:{name:'tom',age:{rAge:40,sAge:29},hobby:['抽烟','喝酒','烫头'],friends:[{name:'jerry',age:35},{name:'tony',age:36}]}}})</script>
</html>
实现效果:
我们可以看到即使是层层递进的属性也都有对应的get和set,而hobby这个数组中的0,1,2 却没有对应的get和set,那也就意味着当有一天我们需要改变这个数组中的0,1,2的数据的时候,数据能被改掉,但是Vue监测不到,也就不会引起页面的更新。所以这也就解释了为什么在1中我们想直接改掉数组中的数据,但是页面上却没有任何变化的问题。
我们这里再看一下这问题:
那么Vue不是靠get和set实现数组监视的,又是靠什么呢?怎么才能让Vue监视到我们修改了数组中的值呢?
因为数组本身就有很多方法,所以Vue在针对数组做操作的时候,就把监视搞到了数组的那些方法上。
push 新增一个元素到末尾
pop 删除末尾的元素
shift 删除第一个元素
unshift 在开头新加一个元素
splice 在数组的指定位置插入一个元素/ 在指定位置删除一个元素/ 替换掉指定位置的某个元素
js数组的splice()方法_爱米酱的博客-CSDN博客第一个参数(起始位置),第二个参数(删除的项数),第三个参数(插入任意数量的项)三个参数,第一个参数(起始位置),第二个参数(0),第三个参数(插入的项)两个参数,第一个参数(要删除第一项的位置),第二个参数(要删除的项数)...https://blog.csdn.net/qq_37050372/article/details/125803856sort 对数组进行排序
reverse 反转数组
可以看到我们上面列举出来的这七个数组方法,都是可以修改数组的,所以Vue就规定,只有调用了数组身上的这7个可以修改数组的方法,Vue才会承认数组被修改了。
那么下面我们再用这些方法试试
我们可以看到,只要调用了数组的这些修改方法,那么页面上就会发生变化,那也就意味着修改操作被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>列表排序</title><!--引入Vue--><script type="text/javascript" src="../js/vue.js"></script><style></style>
</head>
<body><!--准备好一个容器--><div id="root"><!-- 遍历列表 --><h2>人员列表</h2><button @click="updateMei">更新马冬梅的信息</button><ul><li v-for="(p,index) in persons" :key="p.id">{{p.name}} -- {{p.age}} -- {{p.sex}}</li></ul></div>
</body><script type="text/javascript">Vue.config.productionTip = false //阻止Vue在启动时生成生产提示const vm = new Vue({el:'#root',data:{persons:[{id:'001',name:'马冬梅',age:30,sex:'女'},{id:'002',name:'周冬雨',age:31,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}]},methods: {updateMei(){// this.persons[0].name = '马老师' //奏效// this.persons[0].age = 50 //奏效// this.persons[0].sex = '男' //奏效//this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})}},computed:{}})</script>
</html>
实现效果:
那么这个时候又有同学会有疑问,Vue是怎么监测到我们使用了数组的这些修改方法的呢?
Vue在这里使用了一种技术,叫包装。
什么意思呢?也就是说当我们在使用vm._data.student.hobby.push('学习')中的push的时候,这里的push已经不是数组中原汁原味的push了。也就是Array.prototype.push
我们先来验证一下,它和普通数组是否相等。
可以看到普通数组是和 Array.prototype.push相等的
我们再看看被Vue管理的数组中的push方法是不是还是原汁原味的。
可以看到,这个push已经不是原本的push了。
vm._data.student.hobby.push('学习')中的push,其实是Vue给我们写的push,它在这个push方法中做了两件事,第一件事就是先调用了正常的数组的push方法。第二件事就是重新解析模板,生成虚拟DOM那一套流程。
也就是说Vue对数组的监测其实是靠包装数组身上常用修改数组的方法实现的。
具体的我们可以在Vue官网找到详细解释:
那么是不是我们只能通过这些数组方法来让页面发生和数据的联动呢?
其实也不是,我们可以使用刚刚说过的Vue.set,也能实现同样的效果。下面我们试试
我们看到,同样可以实现。
3.总结
Vue监视数据的原理:
1.Vue会监视data中所有层次的数据
2.如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据
(1)对象中后追加的属性,Vue默认不做响应式处理
(2)如果需要给后添加的属性做响应式,请使用如下API
Vue.set(target,key,value)
或
vm.$set(target,key,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的根数据对象添加属性
这篇关于29.Vue监测数据改变的原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!