一、引入

首先画一个简单的图。

我们在写Vue的时候总会和数据打交道,将我们的目标数据写在data中,然后在template的差值表达式中通过{{xxx}}的格式可以响应式的渲染数据。当data中的数据改变时,这里橙色的线就会引起差值表达式的变化。那么问题来了,我们如何监测到data中数据的改变呢?这里就涉及到了Vue监测数据的问题。

二、监测对象

2.1 为什么需要监测对象

首先写出需要用到的静态页面。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>更新数据</title><script type="text/javascript" src="../js/vue.js"></script>
</head>
<body><div id="root"><ul><li v-for="p in persons" :key="p.id">{{p.name}} - {{p.age}} = {{p.sex}}</li></ul></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data:{persons: [{id:'001',name:'黑猫几绛',age: 20, sex: '未知'},{id:'002',name:'白猫几绛',age: 23, sex: '男'},{id:'003',name:'阿猫几绛',age: 18, sex: '女'}]}})</script>
</body>
</html>

现在我们假设有一个需求,在页面上添加一个按钮,通过这个按钮可以修改id为002的数据。

<button @click="change">点击修改白猫几绛</button>methods:{change(){this.persons[1].name = '白马'this.persons[1].age = 17this.persons[1].sex = '未知'}}

逐个属性的修改,可以改变id为002的数据,但是这样是否有些繁琐,我们是否可以将这么长的一段修改改为一个对象?

 this.persons[1] = {id:'002',name:'白马',age: 17, sex: '未知'}

此时在浏览器当中点击按钮后,页面并没有任何反应。然而出乎意料的是,在vm实例对象中,persons[1]的数据确确实实得到了更改!

为了解决这个问题,我们可以先看看另外一个例子,然后通过在浏览器里面输出来查看具体执行过程,在理解如何监测后再回来看这个问题。

2.2数据代理

要想理解数据监测,还需要理解一个前置概念:数据代理。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>监测对象</title><script type="text/javascript" src="../js/vue.js"></script>
</head>
<body><div id="root"></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data:{name: '黑猫几绛',age: 20}})</script>
</body>
</html>

在以前的时候我们如果想访问data里的数据,可以直接通过实例对象,如vm.name,vm.age拿到具体的数值。其实这是做了数据代理后的简化写法。

Vue将data进行了一次加工,将data中的数据放在了_data中,实例对象通过._data的方式可以拿到加工后的数据。只不过属性的值不再直接给出,而是通过get方法来获取,有点类似于面向对象语言如JAVA中获取类内私有变量时使用的getter方法。这里还有一个set方法,当data里面数据值改变的时候,就会调用该set方法,导致重新解析模板,然后生成新的虚拟DOM进行新旧DOM对比,最后更新页面。

在这里其实涉及到了源码方面的问题,我们在本文中不考虑源码,不然太复杂了。简化来说,其实在Vue内部有这样的一种方法:

vm._data = data = new Observer(data)

在Observer函数中先通过Object.keys获取data的所有属性形成一个数组,然后通过遍历该数组,使用Object.defineProperty实现数据劫持,最后将计算的最终结果交给_data。

2.3 对象监测相关API之Vue.set

还是先上静态页面。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>更新数据</title><script type="text/javascript" src="../js/vue.js"></script>
</head>
<body><div id="root"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><hr/><h2>学生姓名:{{student.name}}</h2><h2>学生性别:{{student.sex}}</h2><h2>学生年龄:{{student.age}}</h2><h1>朋友</h1><ul><li v-for="(f,index) in student.friends" :key="index">{{f.name}} - {{f.age}}</li></ul></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data:{name:'MIT',address:'UUU',student:{name: 'tom',age: 20,friends: [{name: 'jack', age: 21},{name: 'mary', age: 20}]},}})</script>
</body>
</html>

也许你会注意到,template中出现的sex其实并不存在于data中。当输出一个对象中不存在的属性值时,会输出undefined,但是Vue经过处理后不会显示undefined值,也就是说此时页面上学生性别后面是空值,控制台也不会出现报错。现在提出一个需求,我们如果需要为学生添加一个新的性别属性。要求是不能改变data中的现有数据,即sex不能直接放入data中。

根据之前介绍的_data,我们可以尝试通过_data直接绑定上sex属性。然后发现并没有渲染到页面上。其实这里和2.1中的问题有点类似了。下面进行分析。

我们先打印一下vm的_data看看。由下图可以清楚的看到,在student对象中成功的添加上了sex属性名和名为未知的属性值。但是继续往下看,在下面的众多get与set方法中并没有出现针对sex的方法,这也就代表着这个后添加的属性并没有成为响应式数据

为了解决这个问题,我们可以使用Vue提供的API,即Vue.set()方法,来让后添加的数据也成为响应式数据。该方法第一个参数target表示要往谁身上添加数据,第二个参数key表示要添加的属性名是什么,第三个参数表示属性值。

此时我们可以通过Vue.set(vm._data.student,'sex','未知'),不仅可以改变_data中的属性,还可以受到监测,改变页面中的数据。还有一个同样效果的api,即vm.$set,用法和Vue.set()相同,参数也是相同,vm.$set(vm._data.student,'sex','未知'),用这两个方法添加上去的数据即可成为响应式数据。

在实现功能后我们可以考虑一下优化简写,由于数据代理,vm.student === vm._data.student,所以可以简写为Vue.set(vm.student,'sex','未知)。

接下来在实际编码中来验证浏览器中的想法,假设通过点击一个按钮来添加sex属性,这里就不再过多描述了,直接贴代码。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>更新数据</title><script type="text/javascript" src="../js/vue.js"></script>
</head>
<body><div id="root"><button @click="addSex">点击按钮新增性别</button><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><hr/><h2>学生姓名:{{student.name}}</h2><h2>学生性别:{{student.sex}}</h2><h2>学生年龄:{{student.age}}</h2><h1>朋友</h1><ul><li v-for="(f,index) in student.friends" :key="index">{{f.name}} - {{f.age}}</li></ul></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data:{name:'MIT',address:'UUU',student:{name: 'tom',age: 20,friends: [{name: 'jack', age: 21},{name: 'mary', age: 20}]},},methods:{addSex(){// 在methoods中,this的指向就是vm实例对象// 这里还可以写成this.$set(this.student, 'sex', '男')Vue.set(this.student, 'sex', '男')}}})</script>
</body>
</html>

但是使用这样的方法有一定的局限性,在添加成功的时候是设置给了data中的student对象,那么假如直接设置给data对象呢?比如假设需要在这里添加一个新的属性建校日期time,如果仍然采用Vue.set方法的话,就会报错。

由此可见该方法只能用于向响应式对象(如这里的data.student)上添加新property,且触发视图更新。因为Vue无法探测普通的新增property。

2.4 为对象赋多个新值

有时候有你可能需要为已有对象赋值多个新 property,比如使用Object.assign()或者是_.extend()。但是,这样添加到对象上的新 property 不会触发更新。因此在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。

比如我想向student新增以前并不存在的身高和体重属性,我可以这样做:

const add = {'height': 180, "weight": 150}
this.student = Object.assign({},this.student, add)

将原来的对象和新增的对象进行合并后赋值给一个空的新对象,然后将该对象赋值给this.student,最终数据可以响应式地添加到student身上。

2.5 实战

现在在data中todo对象并不存在isEdit属性,用户点击列表项的时候再添加该属性。

面对这个需求,根据前面介绍过的内容,我们一开始可以认为直接通过this.$set()绑定、赋初值。这样的确可以让todo响应式的添加上isEdit属性,但是是否考虑过,如果用户第二次点击该todo的时候,又会触发set方法,然而此时已经存在isEdit属性了。

为了解决这个问题,我们可以使用对象方法身上的hasOwnProperty方法来进行一个判断,如果对象中没有这个属性,则为他动态绑定;如果有这个属性,那我们直接修改属性值即可。由于Vue-cli3的一些原因,todo.hasOwnProperty可能报错,那么使用todo.hasOwnProperty.call(todo,'isEdit')强制绑定一下对象即可。

三、监测数组

依旧是先上静态页面。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>更新数据</title><script type="text/javascript" src="../js/vue.js"></script>
</head>
<body><div id="root"><button @click="addSex">点击按钮新增性别</button><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2><hr/><h2>学生姓名:{{student.name}}</h2><h2>学生性别:{{student.sex}}</h2><h2>学生年龄:{{student.age}}</h2><h1>朋友</h1><ul><li v-for="(f,index) in student.friends" :key="index">{{f.name}} - {{f.age}}</li></ul><h1>爱好</h1><ul><li v-for="(h,index) in student.hobby" :key="index">{{h}}</li></ul></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data:{name:'MIT',address:'UUU',student:{name: 'tom',age: 20,friends: [{name: 'jack', age: 21},{name: 'mary', age: 20}],hobby:['吃饭','睡觉','打豆豆']},},methods:{addSex(){this.$set(this.student, 'sex', '男')}}})</script>
</body>
</html>

在控制台中打印vm._data后我们可以清晰的看到,对于student中的hobby数组,数组整体来说有get和set,是响应式的。但是数组里面的元素并非响应式,而是简单的挂在了数组中,这也就是为什么在2.1中,当我们尝试对数组索引下标进行重新赋值时,页面上并没有响应。如果将hobby数组改为对象,再打印一下,就会发现对象中的属性是响应式的。由此我们可以粗略猜测,在Vue中,如果想要通过索引修改数组中的数值,就需要使用某些特殊方法。

方法从何而来?现在我们可以看看Vue文档里面是怎么说的。在文档列表渲染->数组更新检测->变更方法的页面中有这样一段话:

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

要想修改数组里面的数据,我们不妨试试以上的七种方法。看到这些方法也许你会想到Array原型链上的方法,然而在这里并不完全正确。Vue对这些方法进行了一次包裹封装,Vue在解析到这些方法的时候,第一步仍然是正常的调用Array原型链中的方法,第二步则是重新解析模板,这也就是为什么使用这些方法可以触发视图更新,因为重新解析模板后会生成新的虚拟DOM进行新旧DOM对比,最后更新页面。通过vm._data.student.hobby.push === Array.prototype.push这个判断为false可以印证这个想法。

比如在这里,我们如果想要修改hobby当中的打豆豆为学习,应该怎么做?

在接触这些知识点之前我会使用下面这段代码,紧接而来的就是一个错误的渲染。在改变数据后页面中并没有更新。

 this.student.hobby[2] = '学习'

接下来我们试试两种方法,首先试试刚刚介绍的数组操作方法。

this.student.hobby.splice(2,3,'学习')

接下来试试以前介绍过的Vue.set方法。

这里补充一下,在2.3中我们介绍利用该方法修改对象里面的数值时,方法的第二个参数是属性名;而在数组中使用该方法的时候,第二个参数应该为数组中元素的下标。

this.$set(this.student.hobby,2,'学习')

这两个方法都可以成功修改hobby中的数据,并且成功更新到页面中。

总的来说,不可以通过数组的索引值进行赋值。但是可以改变数组的本身指向,在之前的图片中我们已经看到,hobby数组本身是响应式的,是可以通过get和set进行监控的。比如在业务逻辑中使用了filter对数组进行过滤,然后赋值给在data中已经存在的数组。这里我们可以再看看文档中替换数组这一节的介绍。如filter、concat、slice这些方法并不会变更原始数组,而总是返回一个新数组,当使用变更方法时,可以用新数组替换旧数组。

Vue2监测数据的原理相关推荐

  1. Vue2的核心原理剖析

    ✨ 用了这么久的Vue2了你真的 知其然,知其所以然么? ✨今天博主就为大家带来一篇对Vue核心功能的部分剖析\textcolor{pink}{今天博主就为大家带来一篇对Vue核心功能的部分剖析}今天 ...

  2. VUE | key的内部原理、Vue监测数据的原理、Vue.set()和vm.$set()的使用

    目录 目录 一.react.vue中的key有什么作用?(key的内部原理) 1.虚拟DOM中key的作用: 2.对比规则: 3.用index作为key可能会引发的问题: 4.开发中如何选择key? ...

  3. vue2响应式原理解析并实现一个简单响应系统

    vue2响应式原理 Object.defineProperty() 要理解 vue2 数据响应式原理,我们首先要了解Object.defineProperty()方法.下面这些概念引自MDN. Obj ...

  4. 前端清单:Vue2 响应式原理,RN 运行内置 Node,JS 巧用 Proxy 反混淆,GraphQL 优劣思辨...

    前端每周清单第 25 期:Vue2 响应式原理,RN 运行内置 Node,JS 巧用 Proxy 反混淆,GraphQL 优劣思辨,深入 React 动画 作者:王下邀月熊 编辑:徐川 前端每周清单专 ...

  5. vue2/3绑定原理比较 + vue3(ts)基础语法(ref和toRefs、watch)

    一.绑定原理 前情回顾:vue2绑定原理 访问器属性+虚拟DOM树 核心: 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截) 数组: 通过重写数组更新数组一 ...

  6. Day 05- Vue3 Vue2响应式原理

    Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持  --> 给对象扩展属性 -->  属性设置 实现原理: ...

  7. Vue之监测数据的原理

    监测数据原理 vue会监视data中所有层次的数据.只需要开启deep:true 即可. 如何检测对象中数据?通过setter实现数据监视,在 new vue()的时候就要传入监测数据 (就是data ...

  8. 深入vue2响应式原理,在对象或数组新增属性无响应解决方法

    前言 该问题只存在vue2, 基于Object.defineProperty的特性,vue3中的proxy已经解决了该问题,但也存在兼容性问题,例如IE系统任意版本都不支持. vue2是如何追踪数据变 ...

  9. vue2双向数据绑定原理

    先上文字描述: vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发 ...

最新文章

  1. linux下删除有锁的文件夹,Linux 文件夹右下角有锁,解锁
  2. 机器学习导论�_机器学习导论
  3. 编程语言的发展趋势及未来方向(4):动态语言
  4. 计算机原理测试卷一,计算机原理章节测考试试卷一.doc
  5. 【24】淘宝sdk——入门实战之左右悬浮模块
  6. html 自定义属性_五道自测题-你我都应知道的HTML小知识
  7. Spring Session + Redis 实现 Session 共享,附带 Nginx 集群
  8. 一个人做饭有哪些推荐?
  9. 戴维斯大学计算机排名,加利福尼亚大学戴维斯分校计算机科学专业排名第37(2020年USNEWS美国排名)...
  10. 手写curry函数,实现函数柯里化
  11. 百度 android 笔试题库,百度科目一考试题库
  12. ChatGPT办公应用:制作PPT大纲
  13. LaTex - PPT 模板-1 (亲测可用)
  14. RSA的dp泄露 —— 【WUST-CTF2020】leak
  15. 一篇金融IT的论文开题报告
  16. Unity XCode交互(unity调用讯飞的AIUI技能ios的sdk)
  17. Socket基本操作的C++封装--以及Socket通信实践
  18. jedis设置过期时间
  19. 谷歌Chrome浏览器中如何打开或关闭网页的自动翻译功能?
  20. 读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)

热门文章

  1. HTML:1分钟实现简单网站导航栏
  2. (三)jenkins+bonobo git server+windows系统自动化部署springboot项目(远程windows自动化部署)
  3. 安装centos7遇到问题记录:nouveau xxx unknown chipset
  4. 麦克斯韦方程组的组成由来、媒介的电磁性质和边界条件
  5. 根据分钟数换算成天/小时/分钟
  6. 微信公众号使用Emoji表情
  7. 影院卖品既然除了爆米花都卖不动,为什么不降价?
  8. 面试题-list集合删除操作,可能会出现什么问题?
  9. 密码学笔记5 非对称密钥算法
  10. KVM虚拟化使用详解--技术流ken