Vue2核心原理(简易版)-watch功能实现

1,071 阅读1分钟

Vue2核心原理(简易版)-watch功能实现

watch是用来做什么的?

Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

watch是什么?

一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个 property。文档传送 示例:

// 定义watch
let vm = new Vue({
  el: '#app',
  data() {
    return {
      name: 'vue2'
    }
  },
  watch: {
    name(newVal, oldVal) {
      console.log(`watch name change form ${oldVal} to ${newVal}`);
    },
  }
})
// 改变name
setTimeout(() => {
  vm.name = 'vue3'
}, 1000)
// 控制台打印
// watch name change form vue2 to vue3

怎么做这个watch?

  1. 首先我们要回顾vue依赖收集的原理和实现,请看我的前一篇文章,依赖收集

核心原理就是对传入Vue options的watch对象里面的所有的属性,都添加一个自定义watcher,收集对应属性的依赖,然后当这个依赖项更新的时候,不仅通知它的渲染watcher(可能没有),也会通知当前的这个自定义watcher,从而叫到你定义的callback,完成函数内的逻辑。

  1. 怎么拿到更新时的newValue和oldValue?

首先,watcher绑定的时候,默认要执行一遍this.get方法,这样我们就完成了渲染watcher一样的行为,那就是'touch'到这个属性,让依赖项感知到了自己被依赖,添加订阅者,并且此时的值就是初始值,我们把它记录到watcher上,this.value = this.get();。然后呢,每一次watcher更新调用update方法,我们都会重新调用this.get,那么此时的返回值是不是就是最新值了呢?再加上我们先前记录的this.value,很棒,我们有了newValue和oldValue了!

  1. 代码具体实现
    a. 初始化watch对象
    // 添加$watch mixin
    export function stateMixin(Vue) {
      Vue.prototype.$watch = function (key, handler, options = {}) {
        const vm = this;
        options.user = true;
        new Watcher(vm, key, handler, options);
      };
    }
    // 对传入的watch添加自定义watcher
    function initWatch() {
      const vm = this;
      let watchs = vm.$options.watch;
      for (let key in watchs) {
        let handler = watchs[key];
        if (Array.isArray(handler)) {
          for (let i = 0; i < handler.length; i++) {
            createWatcher(vm, key, handler[i]);
          }
        } else {
          createWatcher(vm, key, handler);
        }
      }
    }
    function createWatcher(vm, key, handler) {
      vm.$watch(key, handler);
    }
    
    b. 修改Watcher类,应对自定义watcher属性的情况
    // observer/watcher.js 
    constructor(vm, exprOrFn, cb, options) {
      this.id = "watcher-" + id++;
      this.vm = vm;
      this.exprOrFn = exprOrFn;
      this.cb = cb;
      this.deps = [];
      this.depsId = new Set();
      this.options = options;
      this.user = !!options.user;
    
      if (typeof exprOrFn === "string") {
        this.getter = function () {
          let path = exprOrFn.split(".");
          let obj = path.reduce((pre, currentPath) => {
            return pre[currentPath];
          }, vm);
          return obj;
        };
      } else {
        this.getter = exprOrFn;
      }
    
      this.value = this.get();
    }
    
    get() {
      pushTarget(this);
      // 记录老的值
      const value = this.getter.call(this.vm);
      popTarget(this);
      // 返回老的值
      return value;
    }
    
    update() {
      // vue中的更新操作是异步的
      // 多次调用update 我希望先将watcher缓存下来,等一会一起更新
      queueWatcher(this); 
    }
    
    run() {
      const oldValue = this.value;
      const newValue = this.get();
    
      if (this.user) {
        this.cb(newValue, oldValue);
        // 别忘了把新的值记录为下次的老值
        this.value = newValue;
      }
    }
    

完整代码

github.com/Sotyoyo/do-… 分支do-watch

原文链接

如果你在非掘金地址看到这篇文章,这里有原文传送门,您的点赞是对我最大的支持!

完 🎉

下一讲,computed计算属性实现