computed

首先Vue实例上会存在一个_computedWatchers属性,然后以每个computed属性作为key值生成一个一个的watcher对象。

computed使用一般有两种形式,一个是返回直接返回一个计算函数,另一种是使用对象对象,定义一个get属性方法对应计算函数。总之拿到这个计算函数就行。

  if (!isSSR) {
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
  }

服务端渲染就不考虑了,主要看Watcher构造函数的参数,当前Vue实例,getter就是前面的计算函数,第三个参数不需要传个空函数就行,computedWatcherOptions对应{ lazy: true }

然后依次将computed属性,通过defineComputed方法定义在实例上,依旧是借助Object.defineProperty这个API。

当使用computed中的计算属性,进入定义好的getter方法。通过属性值,可以在前面说的_computedWatchers中找到对应的watcher。

  function createComputedGetter (key) {
    return function computedGetter () {
      var watcher = this._computedWatchers && this._computedWatchers[key];
      if (watcher) {
        if (watcher.dirty) {
          watcher.evaluate();
        }
        if (Dep.target) {
          watcher.depend();
        }
        return watcher.value
      }
    }
  }

dirty值在watcher初始化的时候通过lazy赋值为true,调用watcher上的evaluate方法,该方法就是调用watcherget方法,然后结束后把dirty变成false。当get方法执行时,就会调用computed属性所对应watcher中的计算函数。联系平时使用computed,一般computed计算属性都是依赖其他数据计算出来的,因此在调用计算属性函数时,其他数据(比如data中定义的一些数据)的get方法也会被触发,这样当前watcher(computed的watcher)就会将其他数据对应的dep依赖收集起来。evaluate执行完,此时Dep.target已经变回了外层的watcher,再调用一次watcher.depend方法将computed中watcher所依赖的dep储存进外层的watcher。这样即使data中的数据只在computed中被引用,相对应的dep也会存在于外层wacther中。

更新阶段,data中的值更新,父组件watcher执行update方法将更新操作推进异步队列,而通过lazy属性判断为computed属性对应的watcher,只需把dirty再次变回true,被引用时重复上面的步骤计算即可。

总结一下,computed每个属性都会单独创建一个watcher对象,然后计算属性函数依赖其他数据(一般是data中的),相应的使用这个函数时,触发其依赖数据的依赖收集,watcher.depend将依赖添加到当前Vue实例的watcher中。依赖数据变化时,只需将computed所对应watcher中的dirty状态打开,使用时重新触发计算函数即可。

watch

和computed类似,watch也是定义一个对象,循环watch对象,键值对应需要监听的值,属性值分为数组和非数组类型,createWatcher处理watch的行为.通过整个方法也可以看到,watch监听函数的三种使用方式,如果是对象的话响应函数在handler属性上;如果直接是字符就取实例中对应的methods方法;最后直接定义成一个函数使用。

  function createWatcher (
    vm,
    expOrFn,
    handler,
    options
  ) {
    if (isPlainObject(handler)) {
      options = handler;
      handler = handler.handler;
    }
    if (typeof handler === 'string') {
      handler = vm[handler];
    }
    return vm.$watch(expOrFn, handler, options)
  }

然后看实例上的$watch方法

  Vue.prototype.$watch = function (
      expOrFn,
      cb,
      options
  ) {
    var vm = this;
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {};
    options.user = true;
    var watcher = new Watcher(vm, expOrFn, cb, options);
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value);
      } catch (error) {
        handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
      }
    }
    return function unwatchFn () {
      watcher.teardown();
    }
  }

实现核心就是这个方法,和computed也类似,主要实现还是Watcher构造函数,watcher实例放进vm._watchers中。这里看不同的地方,配置user为true,第二个参数expOrFn这里是个字符对应要监听的数据,通过parsePath返回一个获取该数据的函数,调用watcher上的get方法,数据所对应的dep触发依赖收集,就会把wacther存到dep.subs中了,更新阶段和data中的数据行为基本相同,触发该dep中所有wacther进行update,最终执行run方法,通过user属性再外面多包一层try catch防止报错终止程序。其他如果配置了immediate为ture,会直接调用一次监听函数。最终返回一个函数,函数如果执行就是调用watcher的teardown方法,作用将wacther实例从当前vm和数据依赖中移除,但是没发现具体使用了这个解绑方法的地方。


RaKL
209 声望10 粉丝