要想理解原理就得看源码,最近网上也找了好多vue初始化方法(8个init恶魔。。。)

因为也是循序渐进的理解,对initComputed计算属性的初始化有几处看得不是很明白,网上也都是含糊其辞的(要想深入必须深入。。。),所以debug了好几天,才算是有点头绪,现在写出来即帮自己再次理下思路,也可以让大佬指出错误

首先,基本的双向绑定原理就不说了,可以去搜下相关教程,还是要先理解下简单的例子

进入正题,先来看下initComputed的源码结构,这之前还是先放一个例子也好说明

图片描述

function initComputed (vm, computed) {
  console.log('%cinitComputed','font-size:20px;border:1px solid black')
  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 ("development" !== 'production' && getter == null) {
      warn(
        ("Getter is missing for computed property \"" + key + "\"."),
        vm
      );
    }
    //minxing---console
    console.log('%cinitComputed 定义watchers[key]=new Watcher(vm getter noop computedWatcherOptions)','color:white;padding:5px;background:black');
   
    if (!isSSR) {
      // create internal watcher for the computed property.
      /**
      * 熟悉的newWatcher,创建一个订阅者,为了之后收集依赖
      * 将例子中的num、lastNum和计算属性comNum进行绑定
      * 也就是说在一个deps中有两个dep,其中的subs分别是
      * dep1.subs:[watcher(num),watcher(comNum)]
      * dep2.subs:[watcher(lastNum),watcher(comNum)]
      * dep3........
      * 请看前面的例子,页面html中并没有渲染{{lastNum}};按理说就不会执行lastNum的getter
      * 从而就不会和计算属性进行关联绑定,如果更改lastNum就不会触发dep2的notify()发布
      * 自然也就不会在页面看到comNum有所变化,但是运行后却不是这样,为什么呢
      * 这就引出这个initComputed的下面方法了---依赖收集(watcher.prototype.depend)!
      * 当时也是看了好久才知道这个depend方法的作用,后面再说
      * 首先先来提个头,就是下面代码中watcher中这个getter
      * 其实就是function comNum() {return this.num+"-computed-"+this.lastNum;}}
      * 而这个getter什么时候执行呢,会在Watcher.prototype.evaluate()方法中执行
      * 所以watcher中的evaluate()与depend()两个方法都与initComputed相关
      */
      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.
    // 经过判断后定义计算属性---(关联到vm的data上面)
    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);
      }
    }
  }
}

defineComputed方法

这个方法比较简单主要就是将计算属性绑定到vm上,重要的下面的createComputedGetter方法

function defineComputed (
  target,
  key,
  userDef
) {
  console.log('%cdefineComputed','font-size:20px;border:1px solid black')
  var shouldCache = !isServerRendering();
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef;
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop;
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop;
  }
  if ("development" !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        ("Computed property \"" + key + "\" was assigned to but it has no setter."),
        this
      );
    };
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter (key) {
  console.log('createComputedGetter key',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
    }
  }
}

createComputedGetter方法,主要做了两件事

1 求计算属性的值---利用上面说的调用watcher.evalute()方法,执行watcher中的getter

2 依赖收集绑定,这点最重要,就是上面说过的如果没有在html中对计算属性相关联的data属性(lastNum)进行页面渲染的话,watcher.depend()此方法就会执行这个依赖收集绑定的作用dep.subs[watcher(计算属性),watcher(计算关联属性1),...],这样的话当你更改lastNum就会触发对应的dep.notify()方法发布通知订阅者执行update,进行数据更新了,而如果将watcher.depend()方法注释掉,而页面中将lastNum渲染({{lastNum}}),此时watcher.evalute()会执行watcher.get从而将此计算watcher推入dep.target中,而渲染lastNum执行getter的时候就会将此watcher加入依赖,所以也会将lastNum和计算属性关联到dep中

function createComputedGetter (key) {
  console.log('createComputedGetter key',key);
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        console.log('createComputedGetter watcher evaluate===========');
        //求值
        watcher.evaluate();
      }
      if (Dep.target) {
        console.log('createComputedGetter watcher depend===========');
        //依赖收集
        watcher.depend();
      }
      console.log('%ccreateComputedGetter watcher.value is','color:blue;font-size:40px',watcher.value);
      return watcher.value
    }
  }
}

为了更好的说明下,截两张图(都是基于最上面的html配置哦)

图组一

注释掉watcher.depend()方法,此时deps中没有dep:id4
其实dep:id4在内存中已经定义好了但是没有加入到deps中(因为没有进行依赖收集)
而dep:id5和id6就是上面的数组和递归数组中元素的dep
图片描述

dep:id3 就是
图片描述
这下是不是很清楚了

图组二

进行依赖收集后的deps
图片描述

综上,计算属性基本的原理就是这样了,主要是自己的理解,有不对的地方还请指出,哎,还是写代码舒服点,打字描述太累。。。


呼啦星星星
80 声望7 粉丝

推倒前端这只伪娘!!!