关于computed

前言:

前言就是有了前几篇的基础对于vue相应式原理的初步了解之后,再去看这两个东西会方便很多。写这篇文章是为了一个梳理,还有一些其他的原因,年底再说。

先看computed

computed是在initState的时候,并且在初始化data之后,进行初始化的,来看看初始化的时候它干了什么:

function initComputed (vm, computed) {
    // $flow-disable-line
    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];
      var getter = typeof userDef === 'function' ? userDef : userDef.get;
      if (getter == null) {
        warn(
          ("Getter is missing for computed property \"" + key + "\"."),
          vm
        );
      }

      if (!isSSR) {
        // create internal watcher for the computed property.
        watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions
        );
      }

      // component-defined computed properties are already defined on the
      // component prototype. We only need to define computed properties defined
      // at instantiation here.
      if (!(key in vm)) {
        defineComputed(vm, key, userDef);
      } else {
        if (key in vm.$data) {
          warn(("The computed property \"" + key + "\" is already defined in data."), vm);
        } else if (vm.$options.props && key in vm.$options.props) {
          warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
        }
      }
    }
  }

两参数vm computed就是当前的vm组建和实例上的computed属性,说的通俗点就是new Vue的时候写的那个computed,然后我直接关注for in 里的代码:这里拿到computed上的key和对应的value之后,第一步是取得computed里key对应的value,这里value可能会是一个函数,一个对象,如果是函数就直接取,如果是对象就取他的get方法,然后会为当前的key生成一个watcher:

watchers[key] = new Watcher(
          vm,
          getter || noop,
          noop,
          computedWatcherOptions
 );
 //computedWatcherOptions 等于 var computedWatcherOptions = { lazy: true };

这里啊,这个lazy如果你对watcher构造函数的源码模糊了,建议重新打开一个窗口看看,computed从源码角度来讲,为什么data里的值变了之后才会跟着变呢,是因为这个地方,

第二步是判断当前key是否与props或data里边的key重复,有则警告,没有就调用defineComputed:

function defineComputed (
    target,
    key,
    userDef
  ) {
    var shouldCache = !isServerRendering();
    if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = shouldCache
        ? createComputedGetter(key)
        : createGetterInvoker(userDef);
      sharedPropertyDefinition.set = noop;
    } else {
      sharedPropertyDefinition.get = userDef.get
        ? shouldCache && userDef.cache !== false
          ? createComputedGetter(key)
          : createGetterInvoker(userDef.get)
        : noop;
      sharedPropertyDefinition.set = userDef.set || noop;
    }
    if (sharedPropertyDefinition.set === noop) {
      sharedPropertyDefinition.set = function () {
        warn(
          ("Computed property \"" + key + "\" was assigned to but it has no setter."),
          this
        );
      };
    }
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }
  //这里
  var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
  };

直接看最后一句,vue的老套路了,劫持一个对象的属性的get set,还有一层作用是执行vm.key的时候把这个操作代理到了当前定义的get上,其实computed的set不怎么常用,那来看看这里的get是什么:shouldChache是isServerRendering的执行结果取反的结果,isServerRendering 字如其名自然跟服务端渲染相关,看源码的话,当在服务端运行的时候此值才有可能取真值,那这里他就是false,然后,shouldChache自然取true,接着走,然后computed对应的value一般为function,所以这里执行createComputedGetter:

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
      }
    }
  }

这里实际是把key闭了一个包,返回的函数呢,就是sharedPropertyDefinition的get,所以,此后某个地方访问vm.key的时候这个闭包函数就会执行,再看一下这个函数执行的细节,第一步取得当前computed里key对应的watcher实例,然后判断watcher.dirty是否为true,这个dirty和上边提到的lazy在实例化watcher的时候用的是同一个值,在初始化所有computed的时候他们都为true,因此第一次渲染的时候,这个地方会执行到watcher.evaluate():

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

evaluate很简单,计算value,标记dirty为false,这里的get前面我们也应该讲过:他做了一件事情就是将当前watcher pushTarget,然后调用watcher实例的getter方法,getter方法就是初始化watcher的时候传进去的那个getter,那这个getter里的东西呢,我们注意到computed里一般都会访问到当前data的get方法,而前几篇讲了在get方法中,我们就能收集到当前的watcher,因此此时就会完成依赖收集。然后computed就定义完毕了。

posted @ 2019-10-11 20:09  子龙_子龙  阅读(34)  评论(0编辑  收藏  举报