vue响应式原理及项目中常见的坑
vue 的响应式原理是核心是通过 es5 的保护对象的 object.defindeproperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 wacher,观察者 wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 dom 树,vue 框架会遍历并对比新虚拟 dom 树和旧虚拟 dom 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 dom 树上。
虚拟dom (virtaul dom): 用 js 对象模拟的,保存当前视图内所有 dom 节点对象基本描述属性和节点间关系的树结构。用 js 对象,描述每个节点,及其父子关系,形成虚拟 dom 对象树结构。
项目中常遇到的关于vue响应式的记录与总结:
因为只要在 data 中声明的基本数据类型的数据,基本不存在数据不响应问题,所以重点介绍数组和对象在vue中的数据响应问题,vue可以检测对象属性的修改,但无法监听数组的所有变动及对象的新增和删除,只能使用数组变异方法及$set方法。
1. 向响应式的数组或者对象中修改已有的属性的方法
当想要修改对象或者属性,并非新增属性时,一个已经在 data 中声明过的响应式数据,可以直接操作改变,数据改变会经过上图的步骤,触发视图改变。直接obj.xxx = xxx 即可,数组除外,但是后台传过来的 json 数组,数组中嵌套的对象也可以直接修改数组中的对象,因为 object.defindeproperty 的缺陷导致无法监听数组的变动,但始终会深度遍历data中数据,给数组中嵌套的对象添加上 get 和 set 方法,完成对对象的监听。
2. 向响应式的数组或者对象中新增一个响应式的属性的方法this.$set()或者数组变异方法
即使是一个后台传过来的 json 数组,也可以使用this.$set向数组中的其中一个对象中添加一个响应式的属性,例如 this.$set(arr[0], 'xxx', xxx) 。或者使用数组变异方法例如splice,更多数组变异方法可以参考vue文档。
3. data中声明过的数组或者对象,整体替换数组或者对象保持响应式
向响应式的数组和对象替换为新的响应式数据,可直接复制,因为data中声明的数据已经添加了访问器属性setter,当重新赋值一个新的堆内存地址时,该数组或者对象也会被循环遍历添加访问器属性,所以也是有响应式的。
4. vue无法监听对象的新增和删除,直接通过obj.xxx = xxx新增一个没有的属性,修改当前组件的一个响应式的数据重新触发当前组件re-render,可以让非响应式数据也保持更新状态(特殊情况) 。
给一个数据添加一个非响应式的数据,例如一个已经在data中声明过的数据obj,obj.xxx=xxx,新增一个原本没有的数据,当你继续修改组件中一个其他的响应式数据,该obj也会同步更新到最新的数据另一种情况,当你向一个对象或者数组中同时增加一个响应式和非响应式数据,非响应式数据也会同步更新到页面。例如修改页面的其他响应式数据或者 this.$forceupdate()。
图中this.haveselectedpeople是data中声明过的一个空数组,此时用非变异方法foreach循环数组向每一项添加三个非响应式数据和两个响应式数据,可以使添加的非响应式数据也有实时响应的效果,不建议这样做,因为会使代码难以理解,不过向数组中的每一项循环添加属性使用this.$set是可以经常利用的。
object.defindproperty虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属性进行数据劫持,所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,就能监听到对象的数据变化无法监听到数组的变化,proxy就没有这个问题,可以监听整个对象的数据变化,所以下个vue版本会用用proxy代替definedproperty