vue-computed原理

2020-04-25  本文已影响0人  努力学习的小丸子

结论

1、computed属性也是响应式的;(前提是其依赖的是响应书数据,若如下则计算属性将不会更新)】

computed: {
  now: function () {
    return Date.now()
  }
}

2、computed属性是有缓存功能的,只有依赖的数据发生改变,才会进行重新计算;
3、页面的刷新不是computed属性通知的,而是computed依赖的data通知的;
4、一个订阅者对应一个watcher。而一般的data是数据源,每个数据源都有一个Dep,Dep中方的是一个Watcher列表;当data发生改变时,会通知Dep中的Watcher进行更新。

监听过程描述

在初始化vue实例时,会调用InitState()方法,如果配置了computed属性,会调用initComputed方法;
其他的属性,如:data、prop、methods和watch也是同样的操作。

使用发布订阅模式来监听计算属性,也会监听互相依赖的属性。
当依赖链的一环数据发生变化时,都会触发Dep的nofity()方法,会通知Watcher去更新数据,即执行update()方法。
源码分析:
第一步:

function initComputed (vm, computed) {
    //创建一个空对象给_computedWatchers
    var watchers = vm._computedWatchers = Object.create(null);
    // computed properties are just getters during SSR
    var isSSR = isServerRendering();

    for (var key in computed) {
      var userDef = computed[key];
      //如果计算属性不是一个方法,name则必须为其定义get方法,否则会报错
      var getter = typeof userDef === 'function' ? userDef : userDef.get;

      if (!isSSR) {
        // 为每一个计算属性创建一个观察者,传入vue实例和{lazy:true}
        watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions //初始化定义为{ lazy: true }
        );
      }
      // 判断计算属性是否在data和prop中存在,不存在继续执行,否则宝座
      if (!(key in vm)) {
        defineComputed(vm, key, userDef);
      } else {
        //错误提示
      }
    }
  }

第二步:

function defineComputed (
    target, //vue实例
    key,   //计算属性
    userDef  //计算属性的定义
  ) {
    if (typeof userDef === 'function') {
      //为计算属性计算get/set方法
    } else {
    //为计算属性计算get/set方法
    }
    if (sharedPropertyDefinition.set === noop) {
     //报错,计算属性必须要定义get/set方法。
     //如果是一个方法,并且返回值依赖了data或者prop中的属性,vue会自动添加get/set.否则需要手动定义
    }
    //将计算属性绑定在vue实例上
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }

第三步 获取计算属性的get方法,computed可以缓存的秘密就在get方法中

function createComputedGetter(key) {
  return function computedGetter() {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      //初始化的计算属性的watcher,dirty属性都为true.
      //因为初始化了{lazy:true},在watcher的构造方法中,dirty=lazy
      if (watcher.dirty) {
        watcher.evaluate();
      }
      //如果当前有在计算的watcher,则表明两个Key之间有依赖关系
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value
    }
  }
}

第四步 当计算属性update时,会执行this.dirty为true,因为第一步中new Watcher时,computedWatcherOptions //初始化定义为{ lazy: true }

 Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    console.error('watch-update!!!');
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };

第五步 当页面更新时,会执行第三步的代码,调用watcher.evaluate(),从而调用get方法,并把dirty=false;

Watcher.prototype.evaluate = function evaluate() {
  this.value = this.get();
  this.dirty = false;
};

更新过程描述

1、当computed绑定的数据发生改变时,触发对应的Dep.prototype.notify()方法,通知观察者watcher进行update,update将dirty属性设置为true,并不是computed的watcher去通知页面更新。
2、同时触发页面更新,页面会去读取computed的值,发现dirty属性为true,因此会重新计算,不会读取缓存。

问答

Q : computed依赖的属性发生变化到页面发生变化,流程是怎样的?
A : 详见更新过程描述。computed月老身份的由来。页面使用了computed属性时,computed的订阅中心会添加页面的watcher,于此同时,computed会将页面介绍给自己依赖的data,这样data的订阅中心既有了computed属性,也有了页面。

上一篇 下一篇

猜你喜欢

热点阅读