vue中非原始值的响应方案(二)

66 阅读14分钟

如何代理Object

对于对象的代理,不仅仅是get()和set()这两个简单的方法进行拦截,操作对象的方法有很多,比如in,delete,for...in这些,vue中充分利用Proxy和Reflect对这些操作进行拦截

in操作符

    const obj = {foo:1}
    const p= new Proxy(obj,{
        has(target,key){
            track(target,key)
            return Reflect.has(target,key)
        }
    })
    
    effect(()=>{
        'foo' in p
    })

in操作符对应拦截的方法是has,其他的操作基本都有对应的方法可以拦截

for...in操作符

    const obj = {foo:1}
    const ITERATE_KEY=Symbol()
    const p= new Proxy(obj,{
            ownKeys(target){
                tarck(target,ITERATE_KEY)
                return Reflect.ownKeys(target)
            }
    })
    
    effect(()=>{
        for(const key in p){
            console.log(key)
        }
    })

这里为什么要定义一个ITERATE_KEY呢,因为,在Proxy中,ownKeys方法是获取对象所有属于自己的键值,这里没办法与某一个具体的键值产生联系,所以只能构造唯一的key作为标识. 上面的代码会和foo次数联系,当我们给p添加新属性

    p.bar=2

由于对象只有一个foo,for...in只会循环一次,现在我们为它添加新属性,for...in会从一次变成2次.也就是说,添加新属性,也会对for...in循环产生影响,所以要触发与ITERATE_KEY相关联的副作用函数重新执行. 我们现在设置bar,执行set函数,set函数中的trigger函数只会触发与bar相关的副作用函数重新执行,但是for...in函数是与ITERATE_KEY产生联系的,所以并不能正确触发响应. 解决方法如下

        function trigger(target, key) {
            
            const depsMap = bucket.get(target)
            if (!depsMap) return true
            const effects = depsMap.get(key) //当值改变  从桶中取出与该属性对应的副作用函数
            //取得与ITERATE_KEY相关联的函数
            const iterateEffects = depsMap.get(ITERATE_KEY)

            const effectsToRun = new Set() 
            effects && effects.forEach(effectFn=>{
                if(effectFn!==activeEffect){
                    effectsToRun.add(effectFn)
                }
            })
            //将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
            iterateEffects && iterateEffects.forEach(effectFn=>{
                if(effectFn !== activeEffect){
                    effectsToRun.add(effectFn)
                }

            })
            
            effectsToRun.forEach(effectFn=>{
                if(effectFn.options.scheduler){
                    effectFn.options.scheduler(effectFn)
                }else{
                    effectFn()
                }
            })
        }

其实就是把与ITERATE_KEY相联系的副作用函数取出来执行

当我们修改p.foo时,修改属性不会对for...in产生影响,我们无论修改多少次,for...in都只会执行一次,但是我们的代码仍然会再一次执行副作用函数,所以现在要解决这个问题 先对set函数进行改造

            set(target, key, newVal,receiver) {
                //如果属性不存在,则说明是添加属性,否则是已有属性
                const type = Object.prototype.hasOwnProperty.call(target,key)?'SET':"ADD"
                //设置属性值
                const res = Reflect.set(target,key,newVal,receiver)
                //将type作为第三个参数传给trigger
                trigger(target, key,type)
                return res
            },

然后对trigger函数也要进行处理

        function trigger(target, key,type) {
            
            const depsMap = bucket.get(target)
            if (!depsMap) return true
            const effects = depsMap.get(key) //当值改变  从桶中取出与该属性对应的副作用函数


            const effectsToRun = new Set() 
            effects && effects.forEach(effectFn=>{
                if(effectFn!==activeEffect){
                    effectsToRun.add(effectFn)
                }
            })
            //只有当type为ADD时,才触发与ITERATE_KEY副作用函数执行
            if(type==='ADD'){
                //取得与ITERATE_KEY相关联的函数
                const iterateEffects = depsMap.get(ITERATE_KEY)
                //将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
                iterateEffects && iterateEffects.forEach(effectFn=>{
                    if(effectFn !== activeEffect){
                        effectsToRun.add(effectFn)
                    }

                })

            }
            
            effectsToRun.forEach(effectFn=>{
                if(effectFn.options.scheduler){
                    effectFn.options.scheduler(effectFn)
                }else{
                    effectFn()
                }
            })
        }

delete操作符

            deleteProperty(target,key){
                //检查被操作属性是否为对象自己的属性
                const hadKey = Object.prototype.hasOwnProperty.call(target,key)
                //使用Reflect.deleteProperty 完成属性的删除
                const res =  Reflect.deleteProperty(target,key)
                if(hadKey&&res){
                    //只有被删除属性是对象自己属性且删除成功时,才触发更新
                    trigger(target,key,'DELETE')
                }
                return res
            },

还有一点,当delete执行成功,对象属性会减少,会影响for...in循环次数,因此,与ITERATE_KEY相关的副作用函数应该重新执行

        function trigger(target, key,type) {
            
            const depsMap = bucket.get(target)
            if (!depsMap) return true
            const effects = depsMap.get(key) //当值改变  从桶中取出与该属性对应的副作用函数


            const effectsToRun = new Set() 
            effects && effects.forEach(effectFn=>{
                if(effectFn!==activeEffect){
                    effectsToRun.add(effectFn)
                }
            })
            //只有当type为ADD或者DELETE时,才触发与ITERATE_KEY副作用函数执行
            if(type==='ADD' || type==='DELETE'){
                //取得与ITERATE_KEY相关联的函数
                const iterateEffects = depsMap.get(ITERATE_KEY)
                //将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
                iterateEffects && iterateEffects.forEach(effectFn=>{
                    if(effectFn !== activeEffect){
                        effectsToRun.add(effectFn)
                    }

                })

            }
            
            effectsToRun.forEach(effectFn=>{
                if(effectFn.options.scheduler){
                    effectFn.options.scheduler(effectFn)
                }else{
                    effectFn()
                }
            })
        }

合理触发响应

从上面知道,我们需要知道操作类型是'SET','ADD','DELETE'或者是其他类型操作,从而触发响应.但是要合理触发响应,还有很多事情要做 第一个问题,当我们值没有发生变化时,不需要触发响应

    const obj = {foo:1}
    const p = new Proxy(obj,...)
    
    effect(()=>{
        console.log(p.foo)
    })
      //设置p.foo的值,但是值没有变化
    p.foo=1

很简单看出,需要在set()操作里面进行处理

            set(target, key, newVal,receiver) {
                //获取旧值
                const oldVal = trigger[key]
                //如果属性不存在,则说明是添加属性,否则是已有属性
                const type = Object.prototype.hasOwnProperty.call(target,key)?'SET':"ADD"
                //设置属性值
                const res = Reflect.set(target,key,newVal,receiver)
                //与新值对比,只有不全等才会触发响应
                if(oldVal !== newVal){
                    //将type作为第三个参数传给trigger
                    trigger(target, key,type)
                }
                
                return res
            },

这样直接判断肯定是有问题的(不得不佩服vue设计者想的如此周全)

    NaN ===NaN //false
    NaN !==NaN //true
    const obj = {foo:NaN}
    const p = new Proxy(obj,...)
    
    effect(()=>{
        console.log(p.foo)
    })
      //仍然会触发响应,因为 NaN !== NaN为true
    p.foo=NaN

这样就可以避免NaN问题的出现

            set(target, key, newVal,receiver) {
                //获取旧值
                const oldVal = trigger[key]
                //如果属性不存在,则说明是添加属性,否则是已有属性
                const type = Object.prototype.hasOwnProperty.call(target,key)?'SET':"ADD"
                //设置属性值
                const res = Reflect.set(target,key,newVal,receiver)
                //与新值对比,只有不全等才会触发响应
                if(oldVal !== newVal && (oldVal===oldVal || newVal===newVal)){
                    //将type作为第三个参数传给trigger
                    trigger(target, key,type)
                }
                
                return res
            },

处理完的NaN的问题,还有的问题就是原型链的问题,现在先封装一个reactive函数,这就是reactive的原理

    function(obj){
        return new Proxy(obj,{...})
    }

下面就是基于原型链这个问题的例子

        const child = reactive({})
        const parent = reactive({bar:1})
        //使用parent作为child原型
        Reflect.setPrototypeOf(child,parent)
        effect(()=>{
            console.log(child.bar);//1
        })
        //修改child.bar的值,副作用函数会重新执行
        child.bar=2

我们都知道,当对象身上找不到属性,会顺着原型链找,我们这里child本身没有bar属性,值是从原型链上继承的,因为child是响应式数据,因此在副作用函数中访问了parent.bar,又因为parent也是响应式数据,所以,child.bar和parent.bar都与副作用函数产生联系.

那为什么会执行两次呢.那是因为,如果设置的属性不在对象上,那么会取得原型,因为parent是代理对象,所以child和parent的set()函数都会被执行

看一下下面的代码

    //child的set拦截函数
    set(target,key,value,receiver){
        //target是原始对象obj
        //receiver是代理对象child
    }

由于obj没有bar属性,所以会取得原型parent,并执行parent代理对象的set拦截函数

    //parent的拦截函数
    set(target,key,value,receiver){
        //target是原始对象proto
        //receiver仍然是代理对象child
    }

根据这个特点,vue针对与这个问题的解决方法就是判断receiver是否是target代理对象 将原始对象通过"raw"赋值给代理对象 get方法

            get(target, key,receiver) {
                //代理对象可以通过raw访问原始数据
                if(key==='raw') return target
                tarck(target, key)
                return Reflect.get(target,key,receiver)
            },

这样我们就可以在set中判断receiver是不是tatget的代理对象

浅响应与深响应

vue也是考虑很周全,基本在开发中有的情况都想到了.比如下面这种情况

            effect(()=>{
                console.log(obj.foo.bar)
            })
            //修改obj.foo.bar的值,并不会触发响应
            obj.foo.bar=2

副作用函数并不会执行,这是因为,我们zaiget中通过Reflect.get()得到的obj.foo是一个普通对象,并不是一个响应式数据,所以,要在Reflect.get返回的结果进行包装

                    get(target, key, receiver) {
                        //代理对象可以通过raw访问原始数据
                        if (key === 'raw') {
                            return target;
                        }
                        tarck(target, key);
                        //得到原始值结果
                        const res =  Reflect.get(target, key, receiver);
                        if(typeof res ==="object" && res !==null){
                            //调用reactive函数包装为响应式数据返回
                            return reactive(res)
                        }
                        return res
                    },

这段代码就是判断这个Reflect.get()结果是不是一个对象,如果是,则递归调用reactive函数将这结果包装成响应式数据.

但是,并不是所有情况都希望深响应,vue就设计一个shallowReactive,即浅响应,意思就是只有对象第一层是响应的

            const obj = shallowReactive({foo:{bar:1}})
            effect(()=>{
                console.log(obj.foo.bar);
            })
            //obj.foo是响应的,可以触发副作用函数
            obj.foo={bar:2}
            //obj.foo.bar不是响应的,不触发副作用函数
            obj.foo.bar=3

这里我们要封装一个创建响应式数据的函数,同时reactive也要重新设计,vue底层也是差不多这么实现的

//封装createReactive函数,接受参数isShallow,代表浅响应,默认是false,为非浅响应
            function createReactive(obj, isShallow = false) {
                return new Proxy(obj, {
                    get(target, key, receiver) {
                        //代理对象可以通过raw访问原始数据
                        if (key === 'raw') {
                            return target;
                        }

                        //得到原始值结果
                        const res = Reflect.get(target, key, receiver);

                        tarck(target, key);

                        if (isShallow) {
                            return res;
                        } else {
                            if (typeof res === 'object' && res !== null) {
                                //调用reactive函数包装为响应式数据返回
                                return reactive(res);
                            }
                            return res;
                        }
                    },
                    //省略其他拦截操作
            }
            
            
             //封装reactive函数
            function reactive(obj) {
                return createReactive(obj);
            }
            //封装shallowReactive函数,浅响应数据
            function shallowReactive(obj) {
                return createReactive(obj, true);
            }

我们封装一个createReactive函数,将isShallow作为识别浅响应和非浅响应的表示,分别创建浅响应和非浅响应数据

            const obj = createReactive({ foo: { bar: 1 } }, true);
            effect(() => {
                console.log(obj.foo.bar);
            });
            //obj.foo是响应的,可以触发副作用函数
            obj.foo = { bar: 2 };
            //obj.foo.bar不是响应的,不触发副作用函数
            obj.foo.bar = 3;
            
            //1,2

只读与浅只读

在vue中,有一些数据是只读,例如组件接受一个props是一个只读数据,所以要用到readonly将这个数据变成只读数据

            const obj = readonly({ foo: 1 });
            //尝试修改数据,得到警告
            obj.foo = 2;

修改createReactive函数,接受第三个参数,表示只读和非只读

            function createReactive(obj, isShallow = false, isReadonly = false) {
                return new Proxy(obj, {
                    set(target, key, newVal, receiver) {
                        //如果是只读,则打印警告信息并返回
                        if (isReadonly) {
                            console.warn(`属性${key}是只读的`);
                            return true;
                        }
                        //获取旧值
                        const oldVal = trigger[key];
                        //如果属性不存在,则说明是添加属性,否则是已有属性
                        const type = Object.prototype.hasOwnProperty.call(target, key)
                            ? 'SET'
                            : 'ADD';
                        //设置属性值
                        const res = Reflect.set(target, key, newVal, receiver);
                        // target===receiver.raw 说明 receiver是target的代理对象
                        if (target === receiver.raw) {
                            //与新值对比,只有不全等才会触发响应
                            if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
                                //将type作为第三个参数传给trigger
                                trigger(target, key, type);
                            }
                        }

                        return res;
                    },
                    deleteProperty(target, key) {
                        //如果是只读,则打印警告信息
                        if (isReadonly) {
                            console.warn(`属性${key}是只读的`);
                            return true;
                        }
                        //检查被操作属性是否为对象自己的属性
                        const hadKey = Object.prototype.hasOwnProperty.call(target, key);
                        //使用Reflect.deleteProperty 完成属性的删除
                        const res = Reflect.deleteProperty(target, key);
                        if (hadKey && res) {
                            //只有被删除属性是对象自己属性且删除成功时,才触发更新
                            trigger(target, key, 'DELETE');
                        }
                        return res;
                    },
                    //省略其他拦截函数
                });
            }

对于只读属性来说,是不可以设置属性值,也不可以删除属性,所以修改set和deleteProperty这两个拦截函数,当参数isReadonly为true时,阻止修改属性和删除属性

当一个数据是只读时,并不需要它与副作用函数产生联系,所以,也不需要调用track函数

                    get(target, key, receiver) {
                        //代理对象可以通过raw访问原始数据
                        if (key === 'raw') {
                            return target;
                        }

                        //得到原始值结果
                        const res = Reflect.get(target, key, receiver);

                        if (!isReadonly) tarck(target, key);

                        if (isShallow) {
                            return res;
                        } else {
                            if (typeof res === 'object' && res !== null) {
                                //调用reactive函数包装为响应式数据返回
                                return reactive(res);
                            }
                            return res;
                        }
                    },
            //封装readonly函数,只读数据
            function readonly(obj) {
                return createReactive(obj, false, true);
            }

还有一种情况就是对象嵌套,还是深和浅的问题

            const obj = readonly({ foo: { bar: 1 } });
            //还是可以修改
            obj.foo.bar = 2;

这是因为我们只做到了浅只读,没有深只读,所以我们再继续修改我们的createReactive函数

                    get(target, key, receiver) {
                        //代理对象可以通过raw访问原始数据
                        if (key === 'raw') {
                            return target;
                        }

                        //得到原始值结果
                        const res = Reflect.get(target, key, receiver);

                        if (!isReadonly) tarck(target, key);

                        if (isShallow) return res;
                            
                         
                            if (typeof res === 'object' && res !== null) {
                                //调用reactive函数包装为响应式数据返回
                                //数据为只读,则调用readonly函数继续包装
                                return isReadonly? readonly(res): reactive(res);
                            }
                            return res;
                        
                    },
            //封装readonly函数,浅只读数据
            function readonly(obj) {
                return createReactive(obj, false, true);
            }
            //封装shallowReadonly函数,只读数据
            function shallowReadonly(obj) {
                return createReactive(obj, true, true);
            }

我们在创建浅只读对象就只需要在createReactive函数第二个参数传true就行了

代理数组

在js中,数组有很多方法,我们需要知道有关于数组的操作,比如访问元素的值,访问数组长度,for...of遍历数组,还有一系列数组方法等.这个看似比对象复杂,实际上,数组也是对象的,很多方法我们通过拦截对象也可以做到,现在只需要去拦截数组的一些独特的操作

访问arr.length

            const arr = reactive(['foo']);
            effect(() => {
                console.log(arr.length);
            });
            //设置索引1的值,会导致数组长度为2
            arr[1] = 'bar';

因为在索引1赋值,数组长度发生变化,应该触发响应

                    set(target, key, newVal, receiver) {
                        //如果是只读,则打印警告信息并返回
                        if (isReadonly) {
                            console.warn(`属性${key}是只读的`);
                            return true;
                        }
                        //获取旧值
                        const oldVal = trigger[key];
                        //如果属性不存在,则说明是添加属性,否则是已有属性
                        const type = Array.isArray(target)
                            ? //如果是数组,则检测设置的索引值是否小于数组长度
                              //如果是,则为SET操作,否则为ADD操作
                              Number[key] < target.length
                                ? 'SET'
                                : 'ADD'
                            : Object.prototype.hasOwnProperty.call(target, key)
                            ? 'SET'
                            : 'ADD';
                        //设置属性值
                        const res = Reflect.set(target, key, newVal, receiver);
                        // target===receiver.raw 说明 receiver是target的代理对象
                        if (target === receiver.raw) {
                            //与新值对比,只有不全等才会触发响应
                            if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
                                //将type作为第三个参数传给trigger
                                trigger(target, key, type);
                            }
                        }

                        return res;
                    },
            function trigger(target, key, type) {
                const depsMap = bucket.get(target);
                if (!depsMap) return true;
                const effects = depsMap.get(key); //当值改变  从桶中取出与该属性对应的副作用函数

                const effectsToRun = new Set();
                effects &&
                    effects.forEach((effectFn) => {
                        if (effectFn !== activeEffect) {
                            effectsToRun.add(effectFn);
                        }
                    });
                //只有当type为ADD或者DELETE时,才触发与ITERATE_KEY副作用函数执行
                if (type === 'ADD' || type === 'DELETE') {
                    //取得与ITERATE_KEY相关联的函数
                    const iterateEffects = depsMap.get(ITERATE_KEY);
                    //将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
                    iterateEffects &&
                        iterateEffects.forEach((effectFn) => {
                            if (effectFn !== activeEffect) {
                                effectsToRun.add(effectFn);
                            }
                        });
                }

                //当操作类型为ADD切target为数组时,应该取出与length相关的副作用函数
                if (type === 'ADD' && Array.isArray(target)) {
                    const lengthEffects = depsMap.get('length');
                    lengthEffects &&
                        lengthEffects.forEach((effectFn) => {
                            if (effectFn !== activeEffect) effectsToRun.add(effectFn);
                        });
                }

                effectsToRun.forEach((effectFn) => {
                    if (effectFn.options.scheduler) {
                        effectFn.options.scheduler(effectFn);
                    } else {
                        effectFn();
                    }
                });
            }

当修改数组长度为0,导致数组元素被删除,也应该触发响应

            const arr = reactive(['foo']);
            effect(() => {
                console.log(arr[0]);
            });
            //数组长度设为0,导致第0个函数被删除,因此触发响应
            arr.length = 0;
                    set(target, key, newVal, receiver) {
                        //如果是只读,则打印警告信息并返回
                        if (isReadonly) {
                            console.warn(`属性${key}是只读的`);
                            return true;
                        }
                        //获取旧值
                        const oldVal = trigger[key];
                        //如果属性不存在,则说明是添加属性,否则是已有属性
                        const type = Array.isArray(target)
                            ? //如果是数组,则检测设置的索引值是否小于数组长度
                              //如果是,则为SET操作,否则为ADD操作
                              Number[key] < target.length
                                ? 'SET'
                                : 'ADD'
                            : Object.prototype.hasOwnProperty.call(target, key)
                            ? 'SET'
                            : 'ADD';
                        //设置属性值
                        const res = Reflect.set(target, key, newVal, receiver);
                        // target===receiver.raw 说明 receiver是target的代理对象
                        if (target === receiver.raw) {
                            //与新值对比,只有不全等才会触发响应
                            if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
                                //将type作为第三个参数传给trigger
                                //增加第4个参数,功能:ex.数组长度为0,导致数组元素被删除
                                trigger(target, key, type,newVal);
                            }
                        }

                        return res;
                    },

修改trigger函数

function trigger(target, key, type, newVal) {
                const depsMap = bucket.get(target);
                if (!depsMap) return true;
                const effects = depsMap.get(key); //当值改变  从桶中取出与该属性对应的副作用函数

                const effectsToRun = new Set();
                effects &&
                    effects.forEach((effectFn) => {
                        if (effectFn !== activeEffect) {
                            effectsToRun.add(effectFn);
                        }
                    });
                //只有当type为ADD或者DELETE时,才触发与ITERATE_KEY副作用函数执行
                if (type === 'ADD' || type === 'DELETE') {
                    //取得与ITERATE_KEY相关联的函数
                    const iterateEffects = depsMap.get(ITERATE_KEY);
                    //将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
                    iterateEffects &&
                        iterateEffects.forEach((effectFn) => {
                            if (effectFn !== activeEffect) {
                                effectsToRun.add(effectFn);
                            }
                        });
                }

                //当操作类型为ADD切target为数组时,应该取出与length相关的副作用函数
                if (type === 'ADD' && Array.isArray(target)) {
                    const lengthEffects = depsMap.get('length');
                    lengthEffects &&
                        lengthEffects.forEach((effectFn) => {
                            if (effectFn !== activeEffect) effectsToRun.add(effectFn);
                        });
                }

                //如果操作目标为数组,并且修改length属性
                if (Array.isArray(target) && key === 'length') {
                    //对于索引大于或者对于length值的元素
                    //需要把全部相关联的副作用函数取出来添加到effesToRun中等待执行
                    depsMap.forEach((effects, key) => {
                        if (key >= newVal) {
                            effects &&
                                effects.forEach((effectFn) => {
                                    if (effectFn !== activeEffect) effectsToRun.add(effectFn);
                                });
                        }
                    });
                }

                effectsToRun.forEach((effectFn) => {
                    if (effectFn.options.scheduler) {
                        effectFn.options.scheduler(effectFn);
                    } else {
                        effectFn();
                    }
                });
            }

遍历数组

for...in操作

在js中使用for...in遍历数组和用它遍历对象是一样,都可以用ownKeys这个方法进行拦截.但是为数组添加新元素,修改数组长度都会影响for...in循环.我们要继续完善ownKeys这个拦截函数

 ownKeys(target) {
                        //如果操作对象是数组,则使用length作为key建立响应关系
                        tarck(target, Array.isArray(target)?'length':ITERATE_KEY);
                        return Reflect.ownKeys(target);
                    },
            const arr = reactive(['foo']);
            effect(() => {
                for (const key in arr) {
                    console.log(key);
                }
            });
            arr[1] = 'bar'; //副作用函数重新执行
            arr.length = 1; //副作用函数重新执行

for...of操作

            const arr = reactive(['foo']);
            effect(() => {
                for (const key of arr) {
                    console.log(key);
                }
            });
            arr[1] = 'bar'; //副作用函数重新执行
            arr.length = 1; //副作用函数重新执行

不用添加任何方法就可以实现,因为for...of执行的操作我们在前面已经全部拦截完了

数组查找方法

我们知道数组大多数依赖对象基本语义,所以很多情况,我们不需要做特殊处理

            const arr = reactive([1, 2]);
            effect(() => {
                console.log(arr.includes(1));
            });
            arr[0] = 3; //正常输出false

没有问题的情况下肯定要出问题

            const obj = {};
            const arr =  [obj];
            console.log(arr.includes(arr[0])); //正常输出true
            const obj = {};
            const arr = reactive([obj]);
            console.log(arr.includes(arr[0])); 输出false

这是因为arr.includes(arr[0]) ,includes执行是由arr这个代理对象调用的,然而,includes会通过索引访问数组的值,如果元素可代理,则返回代理对象,所以此时的arr[0]并不是原来的


            //定义应该Map实例,存储原始对象到代理对象的映射
            const reactiveMap = new Map();

            //封装reactive函数
            function reactive(obj) {
                //  优先通过原始对象obj寻找之前的代理对象,如果找到则返回已有的代理对象
                const existionProxy = reactiveMap.get(obj);
                if (existionProxy) return existionProxy;
                //否则,创建新代理对象
                const proxy = createReactive(obj);
                //存储到Map中,从而避免重复创建
                reactiveMap.set(obj, proxy);
                return proxy;
            }

这还是有点问题的

            const obj = {};
            const arr = reactive([obj]);
            console.log(arr.includes(obj));//false

这个问题很简单,我们访问了这个对象,这个对象就会被代理,使用原对象访问,自然是找不到的. 解决方法就是重写includes方法 首先,先更改get拦截函数

get(target, key, receiver) {
                        //代理对象可以通过raw访问原始数据
                        if (key === 'raw') {
                            return target;
                        }

                        //如果操作对象是数组,并且key存在arrayInstrumentations上
                        //那么返回定义在arrayInstrumentations上的值
                        if(Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)){
                            return Reflect.get(arrayInstrumentations,key,receiver)
                        }

                        //得到原始值结果
                        const res = Reflect.get(target, key, receiver);
                        //typeof key !=="symbol" => for...of代理
                        if (!isReadonly && typeof key !== 'symbol') tarck(target, key);

                        if (isShallow) return res;

                        if (typeof res === 'object' && res !== null) {
                            //调用reactive函数包装为响应式数据返回
                            //数据为只读,则调用readonly函数继续包装
                            return isReadonly ? readonly(res) : reactive(res);
                        }
                        return res;
                    },

然后自定义includes函数(vue太牛了)

            const originMethod = Array.prototype.includes
            const arrayInstrumentations={
                includes:function(...args){
                    //this是代理对象,先在代理对象中找,将结果存储到res中
                    let res = originMethod.apply(this,args)

                    if(res===false){
                        //res为false为没找到,通过this.raw拿到原始数组,在去查找更新res值
                        res = originMethod.apply(this.raw,args)
                    }
                    return res
                }
            }

除了includes方法,还有其他方法也需要做类似处理,最终代码

            const arrayInstrumentations = {};
            ['includes', 'indexOf', 'lastIndexOf'].forEach((method) => {
                const originMethod = Array.prototype[method];
                arrayInstrumentations[method] = function (...args) {
                    //this是代理对象,先在代理对象中找,将结果存储到res中
                    let res = originMethod.apply(this, args);
                    if (res === false || res === -1) {
                        //res为false为没找到,通过this.raw拿到原始数组,在去查找更新res值
                        res = originMethod.apply(this.raw, args);
                    }
                    return res;
                };
            });

隐式修改数组长度

数组的栈方法,比如push,pop,shift,unshift,还有splice等方法都会修改数组长度

            let shouldTrack = true;
            ['push', 'pop', 'shift', 'unshift', 'splice'].forEach((method) => {
                const originMethod = Array.prototype[method];
                arrayInstrumentations[method] = function (...args) {
                    shouldTrack = false;
                    let res = originMethod.apply(this, args);
                    shouldTrack = true;
                    return res;
                };
            });