vue2响应式原理array部分

实现响应式

  1. 操作类型为数组的属性的属性时不会触发 getter 函数。虽然不可以,但是如果在对操作数组的方法进行修改,使这些方法被调用的时候可以携带一些额外的方法,那么也可以起到 getter 的作用。

    ES6 前 JavaScript 没有提供拦截原型方法的方法,vue 2中是通过挂载一个拦截器,用拦截器中自定义的方法覆盖 Array.prototype 中的方法来实现的。当调用数组方法的时候,沿原型链往上寻找,首先访问到的是拦截器中被改写的方法。逻辑关系如下图:

    image-20210810232647208

    • 定义拦截器

      • Array 原型中七个改变数组自身内容的方法,需要对这些方法进行改写
      • defineProperty方法将需要改造的方法进行封装,封装后,访问 push 方法的时候,就是在执行 mutator 方法
      • mutator 函数拥有数组方法原有的功能,也可以添加一些附加操作
    const ArrayProto = Array.prototype;
    const arrayMethods = Object.create(ArrayProto);
    ;[
        'push',
        'pop',
        'shift',
        'unshift',
        'splice',
        'sort',
        'reverse'
    ].forEach(method => {
        const original = ArrayProto[method];
        Object.defineProperty(arrayMethods, method, {
            value: function mutator(...args) {
                //这里就可以添加其他方法了
                return original.apply(this, args);
            enumerable: false,
            writable: true,
            configurable: true
        })
    })
    复制代码
    • 覆盖 Array 原型

      • 覆盖的范围:不能直接覆盖数组原型,这样会造成全局勿让,因此拦截器只针对响应式数组的原型,所以只需要在 Observer 中使用拦截器覆盖那些即将被转换成响应式数组的原型。

      • 覆盖的方法:

        1. 改写数组的 __proto__ 属性
        2. 有些浏览器不支持使用__proto__属性,直接将拦截器上的方法放到被侦测的数组身上,数组本身拥有了这些方法,无需向原型链上寻找,同样是实现了对原型方法的覆盖
    import { arrayMethods } from './array'
    ​
    //判断__proto__是否可以用
    const hasProto = '__proto__' in {};
    const arrayKeys = Object.getOwnPropertyNames(arrayMethods);
    ​
    export class Observer {
        constructor(value) {
            this.value = value;
            if (Array.isArray(value)) {
                //通过判断是否支持 __proto__ 属性来决定使用哪种覆盖方法
                const augment = hasProto
                    ?protoAugment
                    :copyAugment
                augment(value, arrayMethods, arraykeys)
            } else {
                this.walk(value);
            }
        }
        ....
    }
    ​
    function protoAugment(target, src, keys){
        target.__proto__ = src;
    }
    function copyAument(target, src, keys){
        for(let i = 0; l = keys.length; i < 1; i++){
            const key = key[i]
            def(target, key, src[key])
        }
    }
    复制代码

收集依赖

Array 在getter中收集依赖

在拦截器中触发依赖

不是像之前那样触发依赖和保存依赖都是在definedProperty中了,那么需要把依赖列表保存在一个getter和拦截器都能访问到的对象上,即Oberserver实例上

在 observer 中新建dep实例,然后在defineReactive函数中实例化observer

function defineReactive(data, key, val) {
    let childOb = observe(val); //为数组属性新建observer实例
    let dep = new Dep();
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            // 收集依赖
            dep.depend();
            // 收集数组属性的依赖
            childOb.dep.depend();
            return val;
        },
        set: function (newVal) {
            ...
        }
    })
}
                          
export fucntion observe (value){
        if(!isObject(value)){
            return
        };
        return new Observer(value)
    }                         
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享