vue源码 - watch实现原理

104 阅读1分钟

本文实现一个最基本的watch,当data中的属性改变时,回去触发watch函数,并返回新值和旧值。

<!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>
        let depId = 0
        let watcherId = 0

        const vm = {
            _data: {
                name: "盲僧",
                actionInfo: '会马氏三角杀吗'
            },
            watch: {
                name(newVal,oldVal) {
                    console.log(newVal,oldVal);
                    vm.actionInfo = '会eq闪吗?'
                }
            }

        };

        function render() {
            document.body.innerHTML = '你的' + vm.name + vm.actionInfo
        }

        function defineReactive(target, key) {
            const dep = new Dep();
            Object.defineProperty(target, key, {
                get() {
                    if (Dep.target) {
                        dep.depend(Dep.target);
                    }
                    return vm._data[key];
                },
                set(newVal) {
                    vm._data[key] = newVal;
                    dep.notify();
                },
            });
        }

        class Dep {
            constructor() {
                this.subs = [];
                this.id = depId++
            }
            depend() {
                Dep.target.addDep(this)
            }
            notify() {
                this.subs.forEach((watcher) => watcher.update());
            }
            addWatcher(watcher) {
                this.subs.push(watcher)
            }
        }

        const stack = []
        Dep.target = null

        function pushStack(watcher) {
            stack.push(watcher)
            Dep.target = watcher
        }
        function popStack() {
            stack.pop()
            Dep.target = stack[stack.length - 1]
        }

        class Watcher {
            constructor(vm, effectFn, options = {},cb) {
                this.getter = effectFn;
                this.vm = vm;
                this.id = watcherId++
                this.depsId = new Set()
                this.deps = []
                this.isWatch = options.isWatch
                this.cb = cb
                this.get();
            }
            get() {
                pushStack(this)
                this.value = this.getter();
                popStack()
                return this.value
            }
            update() {
                if(this.isWatch){
                    const oldVal = this.value
                    const newVal = this.get()
                    this.cb(newVal,oldVal)
                }else{
                    this.get();
                }
            }
           
            addDep(dep) {
                if (!this.depsId.has(dep.id)) {
                    this.depsId.add(dep.id)
                    this.deps.push(dep)
                    dep.addWatcher(this)
                }
            }
        }

        function observer(obj) {
            Object.keys(obj).forEach(key => {
                defineReactive(vm, key)
            })
        }

        function initWatch() {
            const watch = vm.watch
            for (let key in watch) {
                if(typeof key ==='string'){
                    const getter = function (){
                        return vm[key]
                    }
                    const cb = watch[key]   
                    new Watcher(vm,getter,{isWatch:true},cb)
                }
            }
        }

        observer(vm._data)
        initWatch()

        new Watcher(vm, render)

       setTimeout(()=>{
         vm.name ='亚索'
       },3000)
    </script>
</body>

</html>

总结 - 代码解读

 function initWatch() {
      const watch = vm.watch
      for (let key in watch) {
          const getter = function (){
                return vm[key]
          }
          const cb = watch[key]   
          new Watcher(vm,getter,{isWatch:true},cb)
       }
  }
  1. 其实现原理就是初始化的时候去遍历watch上的属性,并为每个属性去添加一个观察者(Watcher),以上initWatch方法中,声明了一个getter方法 ,一个cb, 这getter的主要目的是触发属性的get,将当前的watcher收集到该属性的dep中,这个cb,很明显就是当属性改变了,需要执行的回调函数。
  get() {
      pushStack(this)
      this.value = this.getter();
      popStack()
      return this.value
  }
  update() {
     if(this.isWatch){
       const oldVal = this.value
       const newVal = this.get()
       this.cb(newVal,oldVal)
     } else {
       this.get();
     }
  }
  1. 上面说到我们在new Watcher的时候会第一次调用这个watcher.get(),那么会将第一次的值存起来,在代码后面我们修改了name的值,那么会触发name的set方法,会去通知相应的观察者watcher更新 - watcher.update()

最后说下执行过程:new Wacther -> 触发watcher.get() -> 触发name的get -> 收集到name的dep中 -> 修改name -> 同时观察者更新watcher.update() -> 拿到旧值,再调用watcher.get()拿到新值,执行回调cb()