vue3-watch原理

98 阅读1分钟
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        const data = {name:'阿锋'}
        const obj = new Proxy(data,{
            get(target,key){
                track(target,key)
                return target[key]
            },
            set(target,key,newVal){
                target[key] = newVal
                trigger(target,key)
            }
        })
        
        let bucket = new WeakMap()
        function track(target,key) {
            if(!activeEffect)return
            let targetMap = bucket.get(target)
            if(!targetMap){
                bucket.set(target,(targetMap = new Map()))
            }
            let deps = targetMap.get(key)
            if(!deps){
                targetMap.set(key,(deps = new Set()))
            }
            deps.add(activeEffect)
            activeEffect.deps.push(deps)
        }

        function trigger(target,key){   
            const targetMap = bucket.get(target)
            if(!targetMap) return
            const effects = targetMap.get(key)
            const cloneDeps = new Set()
            effects.forEach(effect=>{
                if(effect!==activeEffect){
                    cloneDeps.add(effect)
                }
            })
            cloneDeps.forEach(effect => {
                if(effect.options.scheduler){
                    effect.options.scheduler(effect)
                }else{
                    effect()
                }
            })
        }
        let activeEffect
        let  effectStack = []

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

        function cleanup(effectFn){
            effectFn.deps.forEach(deps=>{
                deps.delete(effectFn)
            })
            effectFn.deps.length = []
        }

        function traverse(source,seen = new Set()){
            if(typeof source !=="object" || source === null || seen.has(source)){
                return
            }
            seen.add(source)
            for(let key in source){
                traverse(source[key],seen)
            }
            return source
        }

        function watch(source,cb,options={}){
            let newVal, oldVal,getter
            if(typeof source === "function"){
                getter = source
            }else{
                getter = () => traverse(source)
            }
            
            const job = () =>{
                newVal  = effectFn()
                cb(oldVal,newVal)
                oldVal = newVal
            }

            const effectFn = createEffect(()=> getter(),{
                lazy:true,
                scheduler:() =>{
                    if(options.flusg ==="post"){
                        const p = Promise.resolve()
                        p.then(job)
                    }else{
                        job()
                    }
                }
            })
            if(options.immediate){
                job()
            }else{
                oldVal = effectFn()
            }
        }

        watch(()=>obj.name,(oldVal,newVal)=>{
            document.body.innerText = `旧值:${oldVal}, 新值:${newVal}`
        },{
            immediate:true,
            flush:"post"
        })

        setTimeout(()=>{
            obj.name="细锋"
        },3000)

    </script>
</body>
</html>

总结:

本质上利用了副作用函数重新执行时的可调度性。一个 watch 本身会创建一个 effect,当这个 effect 依赖的响应式数据发生变化时,会执行该 effect 的调度器函数,即 scheduler。这里的 scheduler 可以理解为“回调”,所以我们只需要在 scheduler 中执行用户通过 watch 函数注册的回调函数即可。此外,我们还讲解了立即执行回调的 watch,通过添加新的 immediate 选项来实现,还讨论了如何控制回调函数的执行时机,通过 flush 选项来指定回调函数具体的执行时机,本质上是利用了调用器和异步的微任务队列。