从源码理解vue的响应式二

246 阅读2分钟

1.从上次去理解对象的响应式我们可以知道,对象可以通过重写setter和getter来添加监听的逻辑,但是数组不一样,数组不能重写setter和getter,

但是我们可以利用数组的每一次操作都需要经过其API,所以我们重写原型上的API,在src/core/observer/array中我们可以看到哪些API被重写,重写的目的是当数组变化的时候可以通知对应的依赖实例去重新渲染

function observerArray() {  
    const arrayProto = Array.prototype;  
    export const arrayMethods = Object.create(arrayProto);  
    const methodsToPatch = [    
        'push',    
        'pop',    
        'shift',    
        'unshift',    
        'splice',    
        'sort',    
        'reverse'  
    ]  
    methodsToPatch.forEach(function (method) {    
        Object.defineProperty(arrayMethods, method, {      
            value: function mutator(...args) {        
                let ob = this.__ob__;        
                ob.dep.notify();        
                return arrayMethods.apply(this, args);      
            },      
            writable: true,      
            configurable: true    
        })  
    }
)}

2.思考到这里我们明白了当一个属性的值是数组的时候是如何实现响应式的,可能这个时候你会和我有一样的疑惑,通知对应的依赖可以通过重写API,那么收集依赖呢

export default {  
    data() {    
        return {      
            a: 1,      
            b: 2,      
            c: []    
        }  
    }
}

像上面这种定义方式,实例初始化的时候a b c都会有对应的依赖dep集合,这个时候哪些实例会访问到c是已知的,当我们通过this.a = 3这种方式去改变a的值的时候,自然会触发a对应的setter,this.c = 4这种方式也会触发到c的setter,但是当我们通过this.a.push(1),这种方式是监听不到的,所以我们通过重写数组的API来实现数组的响应式

3.为了处理数组,你们observer就可以改成这样

class Observe {  
    constructor(value) {    
        this.value = value;    
        if (Array.isArray(value)) {      
            observerArray();    
        }    
        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]]);    
        }  
    }
}

总结:数组收集依赖的方式和对象是一致的,但是依赖触发更新的机制是通过覆盖原型的方法实现的,且我们可以从上面的methodsToPatch中得知,如果直接修改数组的下标或者操作length改变数组其实都是监听不到的