vue2.x响应式
基本概念介绍:
Vue框架是如何实现变量的变化侦测的呢?
Object 利用了 Object.defineProperty 进行变量的getter与setter拦截,但数组的实现与 Object 有所不同,下面会从源码层面具体讨论这两种类型的变量如何实现变化侦测。
首先我们需要先了解Vue源码中的三个类:
一、Observer
Observer类负责将复杂类型的变量转化成响应式数据,转化为响应式数据的变量都会带有 '__ob__' 属性,所以 '__ob__' 属性可以作为判断响应式数据的标识。
举个例子:
|
输出台得到的结果:
因为list 和 obj 是复杂类型,被Observer处理成了响应式数据,带有'__ob__' 属性,而num是简单数据类型,所以没有被Observer处理。
二、Watcher
当我们侦测到数据发生变化时,需要通知给谁呢?
需要用到这个数据的地方有很多,可能是我们自定义的,也有可能是模板上的,我们把这些地方统称为watcher,可以理解成依赖。
三、Dep
dep的作用在于收集依赖,当侦测到数据发生变化时,dep会通知他收集的所有watch。
dep 与 watch是多对多的关系,一个dep可以收集多个watch,一个watch可以订阅多个依赖。
知道了这三个类,我们就能理解这张流程图:
数据data进过observer处理变成影响式数据,数据的getter与setter被拦截。
每当有watcher到data读取数据时,会被getter拦截,watcher被当做依赖被dep收集。
每当Data的数据发生变化时会被setter拦截,setter会通知Dep,Dep再通知它收集的所有watcher, watcher再调用它的回调函数。
但我们前面讲过数组的变化侦测实现原理有所不同,当我们通过key读取数组时,一样会被getter拦截。
但是由于Object.defineProperty的setter只能做到对每个key进行拦截,当数组调用原生方法如push或pop时,数组值发生变化,
Object.defineProperty却拦截不到,所以数组值的变化侦测只能通过重写数组的原生方法实现拦截。
接下来我们进一步分析源码:
重写Array原生方法
|
Dep类
|
watcher
|
Observer
|
小结:
一、复杂类型的变量会转化成响应式数据,转化为响应式数据的变量都会带有 '__ob__' 属性,所以 '__ob__' 属性可以作为判断响应式数据的标识。
二、变量侦测原理是利用了Object.defineProperty的getter与setter。Object的在getter中收集依赖,在setter中触发依赖。Array在getter中收集依赖,通过重写Array原型方法触发依赖。
三、目前vue这种侦测方案存在缺陷
(1)由于setter方法只能监听的每个key对应的值变化,如果Object新增或者删除一个key, setter是侦测不到,所以不会触发依赖。
|
(2)由于Array是通过重写Array原型方法触发依赖,所以如果直接修改数组下的元素,是不会触发依赖的。
|
针对vue侦测方案的缺陷,vue提供了vm.$set与vm.$del, 这两个API会在方法内调用dep.notify,所以通过API进行赋值可以触发依赖。
vue3.x响应式
在线demo: http://gavincat.cn:8081/
|
vue3.x响应式 对比 2.x:
1. 3.0采用proxy实现代码侦测, 2.x用Object.defineProperty, proxy不兼容ie, Object.defineProperty可兼容至ie8。
2. 3.0解决了2.x删除、新增属性无法被侦测的局限,所以3.0不需$del, $set来触发依赖了。
3. 3.0用weakMap收集依赖,2.x用数组收集依赖,weakMap能及时释放引用,不占内存。