一、为什么Array的变化侦测和Object的不同?
Object的变化侦测是通过getter收集依赖,setter通知依赖实现的,但是数组的话,一般对于数组的操作都是通过一些原型方法,比如说push,pop,这样的话就不会触发getter和setter了。
二、那怎么追踪变化呢?
ES6之前,js并没有提供可以拦截原型方法的能力,所以vue2中使用自定义的原型方法去覆盖原生的原型方法。 也就是用一个拦截器去覆盖Array.prototype。
三、拦截器
(1)拦截器的构建
拦截器就是一个和Array.prototype一样的Object,只不过里面可以改变数组内容的方法都经过了我们的处理。
其中七个函数能改变数组自身内容,push,pop,shift,unshift,sort,reverse。
对于构建一个拦截器,vue2源码中的处理是:先以Array.prototype原型创建一个arrayMethods空对象,循环遍历这七种方法,然后缓存原始方法,然后用defineProperty在arrayMethods中添加当前方法,在defineProperty的value属性中写个自定义的原型方法实现拦截(就可以在其中通知依赖)
(2)拦截器的覆盖
有了拦截器,想生效就必须去覆盖Array.prototype。
所以在vue2源码中,就是在Observe类里面判断,传进来的value是不是数组,如果是对象的话,就用this.walk()设置为响应式,如果是数组的话,就要进一步判断该浏览器环境__proto__是否可用,如果可以使用__proto__,就直接value._proto_ = arrayMethods,如果不可以使用__proto__,则将arrayMethods的方法用defineProperty放在数组上。
四、收集和通知依赖
Array在getter中收集依赖,在拦截器中触发依赖
(1)依赖的位置
Array的依赖列表就放在Observe类里面,因为这样getter能访问到,拦截器也能访问到。(在源码中,Observe直接里面直接就有this.dep = new Dep())
(2)收集和通知依赖
在getter中有个childOb.dep.depend()用来收集数组的依赖,childOb为数组的Observe实例,如果childOb存在,就调用childOb.dep.depend()收集依赖。
然后前面也说过,就是拦截器中自定义的原型方法中用来收集依赖,所以就在执行方法的同时,调用ob.dep.notify()就能通知依赖了。(这里的ob和ChildOb其实意思是一样的,都是Observe实例,也能判断数据是否为响应式)
五、Array变化侦测的问题
因为vue2是对原型方法重写然后覆盖进行拦截的,所以有些变化是无法侦测到的:
比如:
this.list[0] = 2;
this.list.length = 0;
如果是用proxy就可以解决这个问题,vue3就解决了