vue.js设计与实现(阅读)-五(如何代理Object)

2023-01-10  本文已影响0人  幸宇
理解proxy

vue3是基于proxy代理实现的响应,那么什么事proxy代理,简单的说就是使用proxy可以实现对象的基本语义的代理,基本语义就是对象的基本属性的一些操作,比如读,写,修改,删除等;从而使用proxy对这些操作进行拦截,然而proxy只能对基本的语义进行操作,一些非基本操作,比如函数的调用,我们称之为复合操作,比如obj.fn(),是由两个基本语义组成的,首先读取get到obj.fn这个属性,第二就是函数调用;

理解Reflect

Reflect是一个全局对象,它里面有很多方法比如,get,set,apply等,在proxy拦截器中的方法在Reflect中都能找到,那这个函数的作用是什么?它的功能主要是提供访问对象属性的默认行为,比如以下两个行为是等价的:

obj={foo:1}
//访问属性
console.log(obj.foo) // 1
//Reflect访问
console.log(Reflect.get(obj,'foo')) // 1

既然是等价操作,那么它存在的意义是什么?
主要是Reflect.get可以接收第三个参数receiver,receiver这个参数和我们要实现的相应系统密切相关,receiver可以理解为函数调用中的this,例如上面代码如果填入receiver参数:

console.log(Reflect.get(obj,'foo',{foo:2})) // 输出是2,而不是1

如下代码实现对象的响应代理,对象的读,取,删除,for..in属性操作

const ITERATE_KEY = Symbol()

    let activeEffect;
    const bucket = new WeakMap()
    const effectStack = []
    function effect(fn,options={}){
        const effectFn = ()=>{
            cleanup(effectFn)
            activeEffect = effectFn
            effectStack.push(effectFn)
            let res = fn()
            effectStack.pop()
            activeEffect = effectStack[effectStack.length-1]
            return res
        }
        effectFn.deps=[]
        effectFn.options = options
        if(!effectFn.options.lazy){
            effectFn()
        }
        return effectFn
    }

    function cleanup(effectFn){
        for(let i=0;i<effectFn.deps.length;i++){
            effectFn.deps[i].delete(effectFn)
        }
        effectFn.deps.length = 0;
    }

    function track(target,key){
        if(!activeEffect) return
        let depsMap = bucket.get(target)
        if(!depsMap){bucket.set(target,depsMap=new Map())}
        let deps = depsMap.get(key)
        if(!deps){depsMap.set(key,deps=new Set())}
        deps.add(activeEffect)
        activeEffect.deps.push(deps)
    }

    function trigger(target,key,type){
        let depsMap = bucket.get(target)
        if(!depsMap) return
        let effects = depsMap.get(key)
        const effectsToRun = new Set()
        effects && effects.forEach(effectFn=>{
            if(activeEffect!==effectFn){
                effectsToRun.add(effectFn)
            }
        })

        if(type==='ADD' || type ==='DELETE'){
            const iterateEffects = depsMap.get(ITERATE_KEY)
            // 将与ITERATE_KEY相关联的副作用函数也添加到effectsToRun
            iterateEffects && iterateEffects.forEach(effectFn=>{
                if(activeEffect!==effectFn){
                    effectsToRun.add(effectFn)
                }
            })
        }

        effectsToRun.forEach(effectFn=>{
            if(effectFn.options.scheduler){
                effectFn.options.scheduler()
            }else{
                effectFn()
            }
        })
    }
    function reactive(obj){
        return new Proxy(obj,{
            // has(target,key){
            //     track(target,key)
            //     return Reflect.has(target,key)
            // }
            ownKeys(target){
                track(target,ITERATE_KEY)
                return Reflect.ownKeys(target)
            },
            get(target,key,receiver){
                // 代理对象可以通过raw属性访问原始数据
                if(key==='raw'){
                    return target
                }
                track(target,key)
                return Reflect.get(target,key,receiver)
            },
            set(target,key,newVal,receiver){
                // 先取旧值
                const oldValue = target[key]
                const type = Object.prototype.hasOwnProperty.call(target,key)?'SET':'ADD'
                const res = Reflect.set(target,key,newVal,receiver)
                if(target===receiver.raw){
                    if(oldValue!==newVal&&(oldValue===oldValue||newVal===newVal)){
                        trigger(target,key,type)
                    }

                }
                return res
            },
            deleteProperty(target,key){
                const hadKey = Object.prototype.hasOwnProperty.call(target,key)
                const res = Reflect.deleteProperty(target,key)
                if(hadKey && res){
                    trigger(target,key,'DELETE')
                }
                return res
            }
        })
    }

    const obj = {}
    const proto = {bar:1}
    const child = reactive(obj)
    const parent = reactive(proto)

    // 使用parent作为child的原型
    Object.setPrototypeOf(child,parent)
    effect(()=>{
        // console.log(child)
        console.log(child.bar)
    })
    child.bar = 2

上一篇 下一篇

猜你喜欢

热点阅读