今天同事在开发的时候,对数组类型的数据进行赋值的时候发现视图并未更新。原来我也遇到过当时解决方式是改变赋值的方式,也未深究其原理,在以后的开发中可能因为开发习惯比较好,也再没出现过这种情况。今天就着这个问题剖析下vue
的数据驱动,以及有哪些注意事项(文档中也说了,还是要经常刷文档,每刷一遍都有不同的理解)。
数据驱动的整体思路(可以这样理解,源码的实现过程比这里复杂很多):
- 对于声明的变量,
vue
在数据实例化的时候都会添加一个Getter和Setter,分别负责取值和赋值。 - 对于双向绑定的变量,也就是在DOM中绑定的变量,
vue
都会给他绑定一个Watcher。在赋值触发Setter的时候会触发Watcher,Watcher会重新计算变量的值是否发生了改变,在监听到变量发生变化时会操作具体的DOM改变其绑定的变量来实现更新。
下面就按照上面的过程分析下是如何实现的以及为什么会出现我同事出现的问题。
一、Getter/Setter
先讲下Getter/Setter:
// 简单实现
const data = {};
Object.defineProperty(data, 'names', {
get: () => {
console.log('获取数据');
return ['Li', 'Zhang'];
},
set: (newVal) => {
console.log('更新数据,参数是' + newVal);
}
});
data.names; //获取数据
data.names = ['Wang']; //更新数据,参数是walsr (触发了set)
data.names.push = 'Wang'; //获取数据 (未触发set)
data相当于vue
中的vm.$data,get是一个给names属性(相当于声明的names变量)提供 Getter 的方法,set是一个给属性提供 Setter 的方法(接受唯一参数)。触发set的时候继而触发了Watcher。到这里同时也回答了为什么vue不支持ie8及以下版本,因为vue用了defineProperty方法来实现监听数据,而defineProperty只支持IE9及以上版本浏览器。
看上面的例子大致可以明白我同事出现的问题在哪里,就是没有触发变量相应的set同时也就没触发变量相应的Watcher。这里我也有个疑问,在这个简单的例子中push并没有触发set,但vue
是会监听到push的。官方文档数组更新检测说明,文档说了能监听到数组的变异方法,至于我同事的问题就是没有用对方法。
二、Watcher
上面的整体思路正常理解起来没问题,但是跟vue源码具体实现Watcher还是有些区别的,我大致说一下我的理解吧,源码我理解的不是很全面,以下仅作参考。这里没有贴源码,只是我个人的理解,具体参考 Vue 源码解析:深入响应式原理。
- 首先在vue实例化的时候会有一个生命周期,其中一个过程的就是调用 vm.initData() 来处理 data 选项。
- 在 initData 中使用了proxy 方法,它的功能就是遍历 data 的 key,把 data 上的属性(也就是在data中声明的变量)代理到 vm 实例上。 proxy 方法是通过 Object.defineProperty 的 Getter 和 Setter 方法实现了代理。这就是为什么会讲 Getter 和 Setter 。
- initData 在把变量代理到vm实例上后会调用 observe(data, this) 方法来对 data 做监听 。对于数组、对象类型的变量,observe会对其每一个属性进行转换,使它们都拥有 Getter、Setter 方法。到这里为止每个变量以及其属性都有了 Getter、Setter 方法,当变量被访问或者更新时,我们就可以追踪到这些变化。
- observe下面有一个 Dep 类,在 Getter 和 Setter 方法调用时会分别调用 dep.depend 方法和 dep.notify 方法,depend 方法的功能是把当前 Dep 的实例添加到当前正在计算的Watcher 的依赖中,notify 方法功能时遍历所有的 Watcher,调用相应的 update 方法,以此来达到更新的作用。
上述过程最重要的就是:通过observe(底层还是基于defineProperty)实现了变量和Watcher之间的关联,Watcher类再去实现相应DOM的更新。Watcher类的解析等我在深挖下源码在系统的梳理把,现在理解的可能不时很准确。
到这里基本上整个数据驱动流程就介绍完了,下面说下数据驱动的注意事项
三、注意事项
var vm = new Vue({
data: {
items: ['a', 'b', 'c'],
person: {
name:'zhengsy'
}
}
})
- 数组:当你利用索引直接设置一个项时,例如:
vm.items[0] = 'd'
。 - 数组:当你修改数组的长度时,例如:
vm.items.length = 5
。 - 对象:当你添加或删除对象属性时:例如:
vm.person.age = 33
- 对象:当你使用Object.assign()时:例如:
Object.assign(vm.person, { age: 33 })
相应的解决办法:
vm.$set(vm.items, 0, 'd')
或者vm.items.splice(0, 1, 'd')
vm.items.splice(5)
vm.$set(vm.person, 'age', 33)
vm.person = Object.assign({}, vm.person, { age: 33 })