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

127 阅读3分钟

Vue是用proxy代理对象读和写的操作,我们不能纯粹拦截get和set操作,比如还有拦截for...in?track函数如何追踪拦截for..in操作,还有拦截Map,Set,WeakMap,WeakSet,对于非原始值(引用数据类型)的代理还需要更多考虑 首先,要考虑那么多非原始值操作,首先要理解Proxy和Reflect

1.理解Proxy和Reflect

Proxy最基本的功能,就是代理对象,拦截对象的操作,最常用的就是get()和set()读和写.

这里的代理指的就是对象基本语义的代理,允许我们拦截并重新定义一个对象的基本操作.

什么又是基本语义,比如get(),set()这种对象的基本操作,还有下面拦截函数的调用也是基本操作

在JavaScript中,函数也是一个对象,所以Proxy也可以拦截函数的调用操作,拦截函数就是用Proxy中的apply拦截函数

    const fn=(name)=>{
        console.log('我是',name)
    }
    const p2 = new Proxy(fn,{
        //使用apply拦截函数调用
        apply(target,thisArg,argArray){
            traget.call(thisArg,...argArray)
        }
    })

有基本操作那就有非基本操作,比如

    obj.fn()

这种是要先set()进行读取操作,然后读取到了,再继续apply函数调用的操作,这种就叫做复合操作.知道proxy只能代理对象基本操作,后面对set,map进行代理,都是利用proxy这个特点.

理解了Proxy,接下来就是理解Reflect了,这个再官方的定义叫反射,我的理解就是把一些对象的操作反射出来,proxy能够找到的方法都能在Reflect找到.比如Reflect.get

        const obj={foo:1}
        //直接读取
        console.log(obj.foo)
        //使用Reflect读取
        console.log(Reflect.get(obj,'foo'))

但是Reflect和Proxy不同的,它可以接受第三个参数,就是"指定接受者receiver",有点类似于this

        const person = {
            firstName: "John",
            lastName: "Doe",
            get lastName(){
                return this.lastName
            }
        };

        const fullName = Reflect.get(person, "firstName") + " " + Reflect.get(person, "lastName",{lastName:'555'});
        console.log(fullName); //John 555

之前在实现响应式系统文章中,proxy是这样为对象进行代理


    const obj = {foo:1}
    const p = new Proxy(obj,{
        get(target,key){
            track(target,key){
                //没有用Reflect.get读取
                return traget(key)
            }
        },
        set(target,key,newVal){
            //没有使用Reflect.set完成设置
            target[key]=newVal
            trigger(target,key)
        }
    })

之前的响应式系统在这里就会有问题了

    const obj = {
        foo:1,
        get bar(){
            return this.foo
        }
    }

然后我们在副作用函数中访问bar属性

    effect(()=>{
        console.log(p.bar)
    })

在这里,我们执行了getter,访问了this.foo,副作用函数应该会和foo产生联系,但是我们修改这个值

p.foo++

副作用函数并不会执行,问题就出现在访问器函数中的this,在Proxy的get函数中,通过target[key]返回函数值的,这里的target是代理对象的原始对象,也就是obj,target[key]就相当于obj.bar,我们用p.bar访问属性时,getter访问的this是原始对象obj,最终访问的也是obj.foo,只有访问p.foo才能和副作用函数进行类型,进行依赖收集.这时,Reflect就是来解决这个问题的

    const p = new Proxy(obj,{
        get(target,key,receiver){
            track(target,key)
            //使用Reflect.get读取,返回读取到的属性值
            retuen Reflect.get(target,key,receiver)
        }
    })

这里的receiver就是代理对象p,使用Refl.get传入第三个参数,改变访问器函数作用域,将this改为代理对象,这样就成功访问到代理对象p,就可以进行依赖收集