Vue响应式原理-Array的变化侦探

80 阅读2分钟

源码位置

Array的变化侦探

根据上文Vue响应式原理-Object的变化侦探可知,Object的变化是靠setter来进行追踪的,也就是说在getter中收集依赖,在setter中来触发依赖通知变化,而Array中,当然也是在getter中进行依赖收集,但是触发依赖通知就与Object不一样了,Array本身具有7个方法可以对数组进行更改,所以我们可以拦截Array,在它的拦截器中来进行触发依赖通知。

Array的拦截器

地址见:- array.js

//保存Array原来的方法
const arrayProto = Array.prototype
//复制一份arrayProto
const arrayMethods = Object.create(arrayProto)
//Array的7个方法
const methodsToPatch = [
    'push',
    'pop',
    'unshift',
    'shift',
    'splice',
    'sort',
    'reverse'
]

// Array拦截器,触发更新通知
methodsToPatch.forEach(method => {
    // 保存原始方法
    const origin = arrayProto[method]
    //拦截器
    def(arrayMethods, method, function mutator(...args){
        
    })
})

通过以上代码,我们可知,Array触发更新应该在拦截器mutator中触发。但是现在我们还不知道依赖收集在哪里,所以现在要解决的问题是:

  • 如何收集依赖
  • 如何获取到依赖对象呢

对于Object收集依赖是通过在defineReactive中 new Dep(), 然后在getter中通过dep.depend()进行收集依赖。这里依赖可以直接放在defineReactive中的,是因为触发依赖也在defineReactive只中进行,但是Array中收集依赖和触发依赖的位置有明显的区别,我们只要能让defineReactive中和Array拦截器中都能访问到依赖,就能解决上面两点的问题。

所以,我们考虑将Array的依赖收集放在Observer中,并给每一个进行了Observer(响应式)的属性都添加上__ob__来存储这个Observer实例,从而对于述责Array拦截器,只需要通过this.__ob__就能够访问到Observer,从而访问到Observer中的dep,进行依赖触发通知。

所以现在对Observer进行修改(原始Observer见上文Vue响应式原理-Object的变化侦探)

class Observer {
    constructor(value) {
        this.value = value
        this.dep = new Dep()
        def(value, '__ob__', this)
        if(Array.isArray(value)) {
            // 将arrayMethods给value的原型, 实现拦截
            protoAugment(value, arrayMethods)
        }else{
            this.walk(value)
        }
    }
    
    walk(obj) {
        const keys = Object.keys(obj)
        for(let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]])
        }
    }
}
function defineReactive(data, key, val) {
    const dep = new Dep()
    // new Observer(val)
    const childOb = observe(val)
    if(typeof val === 'object') {
        new Observer(val)
    }
    Object.defineProperty(data ,key ,{
        enumerable: true,
        configurable: true,
        get: function() {
            dep.depend()
            return val
        },
        set: function(newVal) {
            dep.notify()
            if(newVal!==val) return newVal
        },
    })
}
// 伪代码
function observe(value, asRootDate){
    if(!isObject(value))
        return
    // __ob__ 存在直接返回,不存在则新建
    let ob;
    if(hasOb(value) && ob instanceOf Observer)
        ob = value.__ob__
    else
        ob = new Observer(value)
}

从以上代码可以看出,此时defineReactive中已经访问到dep依赖收集了。并且每一个进行了响应式的属性身上都有了一个__ob__ 对象,所以此时可以在Array拦截器直接访问

// Array拦截器,触发更新通知
methodsToPatch.forEach(method => {
    // 保存原始方法
    const origin = arrayProto[method]
    //拦截器
    def(arrayMethods, method, function mutator(...args){
        
        let inserted;
        //访问ob即Observer实例
        const ob = this.__ob__
        switch(method) {
            case 'push': 
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                inserted = args.slice(2)
                break;
        }
        // inserted存在, 给新增的inserted添加响应式
        if (inserted) ob.observeArray(inserted)
        
        //通知变化
        ob.dep.notify()
    })
})

以上代码,不仅可以实现Array发生变化后通知变化,对于Array新增了的属性,还可以对新增的属性添加上响应式.