關(guān)于computed源碼

問(wèn):下面代碼從store中取值,如果template中沒(méi)有使用msg, 初始化加載會(huì)打印嗎?其他頁(yè)面改變store的值,這里會(huì)執(zhí)行打印嗎?

computed: {
     msg() {
        console.log(this.$store.state.name)
        return this.$store.state.name
      }
}

答: 不會(huì)。
computed觸發(fā)條件:
1.函數(shù)內(nèi)依賴了vue的屬性
2.這些屬性發(fā)生了改變
3.這些屬性被頁(yè)面引用(觸發(fā)計(jì)算屬性的getter,比如打印這個(gè)計(jì)算屬性)。
這三個(gè)條件同時(shí)滿足,才會(huì)觸發(fā)computed中定義的某個(gè)函數(shù)的回調(diào)

從以上場(chǎng)景來(lái)思考computed的執(zhí)行

一、在初始化data后初始化computed,initState源碼段
function initState (vm) {
  vm._watchers = [];
  var opts = vm.$options;
  if (opts.data) { initData(vm);}
  if (opts.computed) { initComputed(vm, opts.computed); }
   ...
}

二、 initComputed初始化computed。
  1. 實(shí)例化Watcher,
    注意兩個(gè)參數(shù):
    (1)第二個(gè)參數(shù)就是computed中的函數(shù)體getter,在什么時(shí)機(jī)執(zhí)行呢,后面我們到Watcher中去看
    (2)第四個(gè)參數(shù)傳入了lazy: true,將所有的computed watcher存到this._computedWatchers,后面要用的
  2. 執(zhí)行defineComputed
var computedWatcherOptions = { lazy: true };

function initComputed (vm, computed) {
  var watchers = vm._computedWatchers = Object.create(null);

  for (var key in computed) {
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      );
    }

    if (!(key in vm)) {
      defineComputed(vm, key, userDef);
    }
}
三、 defineComputed部分代碼:利用Object.defineProperty使其變?yōu)轫憫?yīng)式,并掛在vm上,get函數(shù)邏輯在createComputedGetter
var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function defineComputed ( target, key,userDef) {
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = createComputedGetter(key)
    sharedPropertyDefinition.set = noop;
  }

  Object.defineProperty(target, key, sharedPropertyDefinition);
}
四、 createComputedGetter:返回一個(gè)函數(shù)給到get

(1)上面初始化中存的this._computedWatchers中如果有這個(gè)watcher,并且dirty就執(zhí)行watcher.evaluate()得到value,并返回value(也就是watcher.value)
(2)記住它:這里有一個(gè)Dep.target,執(zhí)行了depend。后面看到watcher代碼我們繼續(xù)看

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

到這我們就能看到:
1.computed初始化變?yōu)榱隧憫?yīng)式數(shù)據(jù)且掛在到了this上。
2.要想computed的get被執(zhí)行,那就需要這個(gè)響應(yīng)式數(shù)據(jù)被使用。也就是在渲染到視圖層時(shí)(或者this.msg)會(huì)觸發(fā)get拿到最新的value。

重點(diǎn):這里要注意一個(gè)條件:watcher.dirty 必須要dirty是true才會(huì)拿值。watcher.evaluate獲取最新值就會(huì)執(zhí)行computed的函數(shù)體getter。以下代碼我們看看watcher部分源碼
五、Watcher 部分
  1. 這里能看到第二參數(shù)expOrFn就是函數(shù)體getter。第四參數(shù)options中的lazy默認(rèn)傳進(jìn)來(lái)是true,自然的this.dirty也是true。this.value就是undefined。證明初始化的時(shí)候不會(huì)執(zhí)行函數(shù)體getter
class Watcher {
constructor ( vm, expOrFn, cb,options) {
    this.vm = vm;
    vm._watchers.push(this);
    if (options) {
      this.lazy = !!options.lazy;
    } 
    this.dirty = this.lazy; // for lazy watchers
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn;
    } 
    }
    this.value = this.lazy ? undefined : this.get();
  }

}

以上是Watcher的constructor,下面我們看看Watcher中的方法

  1. evaluate還記得嗎,上面我們講到:如果計(jì)算屬性被使用,進(jìn)入計(jì)算屬性的get,watcher.evaluate在dirty為true的時(shí)候執(zhí)行
    (1)通過(guò)Watcher中的get方法拿值: this.get(),在 this.get()中執(zhí)行computed的函數(shù)體getter
    (2)拿到值后this.dirty變成了false
// class Watcher {
以下是Watcher中的方法
  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */。
  evaluate () {
    this.value = this.get();
    this.dirty = false;
  }

  1. 看看get方法
    (1) 在這里,函數(shù)體getter的執(zhí)行了。執(zhí)行函數(shù)體,就會(huì)觸發(fā)依賴值的get進(jìn)行計(jì)算拿到最新值
// class Watcher {
以下是Watcher中的方法
 /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
 // 將自身設(shè)置為訂閱對(duì)象
    pushTarget(this);
    let value;
    const vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value);
      }
      popTarget();
      this.cleanupDeps();
    }
    return value
  }

到這里我們就明白了,初始化的時(shí)候傳入lazy,lazy = dirty = true。所以渲染到視圖層時(shí)會(huì)觸發(fā)get會(huì)執(zhí)行取值,然后dirty變?yōu)榱薴alse。
那么想要再次執(zhí)行就必須有一個(gè)方法去將dirty變?yōu)閠rue,我們找一找

  1. 上面代碼有 pushTarget(this),執(zhí)行函數(shù)體getter后,再 popTarget()

注意這里push之后又pop刪掉了,Dep.target會(huì)變化

Dep.target = null;
const targetStack = [];

function pushTarget (target) {
  targetStack.push(target);
  Dep.target = target;
}

function popTarget () {
  targetStack.pop();
  Dep.target = targetStack[targetStack.length - 1];
}

5.再看看Dep。原來(lái)是一個(gè)發(fā)布訂閱器,可以看到最后執(zhí)行訂閱對(duì)象的update方法

原來(lái) pushTarget(this)是把watcher自己當(dāng)作了訂閱對(duì)象,給別人訂閱,這里watcher就是Dep.target。我們看代碼:下面有一個(gè)Dep. depend,如果執(zhí)行就會(huì)跑watcher上的addDep方法

class Dep {
  constructor () {
    this.id = uid++;
    this.subs = [];
  }

  addSub (sub) {
    this.subs.push(sub);
  }

  removeSub (sub) {
    remove(this.subs, sub);
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice();
    if (!config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id);
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  }
}

看看watcher上的addDep方法,參數(shù)是訂閱器Dep。最后執(zhí)行Dep上的addSub將watcher加入到了訂閱器

// class Watcher {
以下是Watcher中的方法
/**
   * Add a dependency to this directive.
   */
  addDep (dep) {
    const id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  }

看著這里你應(yīng)該明白了:
(1)Watcher是監(jiān)聽(tīng)器,Vue會(huì)提供觀察者去訂閱他,如果觀察者發(fā)現(xiàn)需要更新某個(gè)操作,會(huì)通知Watcher,watcher會(huì)執(zhí)行update進(jìn)行更新。
(2)computed的watcher參數(shù)不一樣,傳入了一個(gè)lazy:true。所以lazy:true具有代表性

我們可以想到:這里的update肯定要將dirty變?yōu)榱藅rue,不然無(wú)法執(zhí)行我們上面說(shuō)到的執(zhí)行函數(shù)體getter

// class Watcher {
以下是Watcher中的方法, 只有computed的watcher的lazy默認(rèn)傳入的是true
update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  }

現(xiàn)在,只要在適當(dāng)時(shí)機(jī)觸發(fā)Dep. notify() 就能實(shí)現(xiàn)update函數(shù)執(zhí)行將dirty更新為true。

思考

(1)誰(shuí)是觀察者來(lái)執(zhí)行Dep. notify() ?自然是依賴的數(shù)據(jù)變化了,就去notify,將dirty改為true
(2)什么時(shí)候執(zhí)行Dep. depend?想一想應(yīng)該也是觀察者,也就是依賴的數(shù)據(jù)變化,我們往下一步步看找答案
(3)還記得上面的createComputedGetter嗎,代碼里面有個(gè)watcher.depend。它做了什么呢?

computed的get
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();
      }
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value
    }
  }
}
// class Watcher {
以下是Watcher中的方法
 /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  }

先解決第一個(gè)和第二個(gè)問(wèn)題:依賴的數(shù)據(jù)是響應(yīng)式的,那initData時(shí)候,響應(yīng)式數(shù)據(jù)的set中一定有notify的執(zhí)行??聪旅娲a驗(yàn)證一下

六、我們回到initState代碼
  1. 初始化data都會(huì)走到observe()
function initState (vm) {
  vm._watchers = [];
  const opts = vm.$options;
  if (opts.data) { initData(vm); } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) initComputed(vm, opts.computed);
}


function initData (vm) {
 // observe data
  observe(data, true /* asRootData */);
}
  1. 再看看Observer:所有掛在的數(shù)據(jù)都走到了defineReactive$$1,還判斷了數(shù)據(jù)是數(shù)組的情況,observeArray是循環(huán)再執(zhí)行observe,這里我們只看普通情況
class Observer {
 
   // number of vms that have this object as root $data

  constructor (value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj) {
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);
    }
  }
}
  1. 繼續(xù)看defineReactive$$1, 確實(shí)在set里面看到了notify,get里面也有Dep.depend()。
function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  const dep = new Dep();
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val;
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}

(1)從get中可以看到,首先實(shí)例化一個(gè)發(fā)布訂閱器 const dep = new Dep()

(2)當(dāng)響應(yīng)式數(shù)據(jù)被觸發(fā)會(huì)判斷Dep.target,Dep.target如果有值(上面說(shuō)過(guò),它就是一個(gè)watcher),就執(zhí)行訂閱器的dep.depend()

(3)對(duì)第二個(gè)問(wèn)題解決:什么時(shí)候執(zhí)行Dep. depend? 在上面代碼中看到了。

class Dep {
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
}

執(zhí)行watcher的addDep方法,
this.newDeps 存儲(chǔ)了該響應(yīng)式數(shù)據(jù)的dep實(shí)例
實(shí)際上就是給第一步實(shí)例的dep注入了watcher,以便下面set中notify的時(shí)候執(zhí)行指定的watcher的update方法,這里就建立了對(duì)應(yīng)關(guān)系。

// class Watcher {
以下是Watcher中的方法
/**
   * Add a dependency to this directive.
   */
  addDep (dep) {
    const id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  }

(4)上面第三個(gè)問(wèn)題問(wèn)到watcher.depend。它做了什么呢?這需要仔細(xì)看看

我們捋一捋整個(gè)流程的順序

第一步:進(jìn)入計(jì)算屬性的get執(zhí)行以下代碼,也就是createComputedGetter

      if (watcher.dirty) {
        watcher.evaluate();
      }
   這里還不慌執(zhí)行,我們?cè)诘谒牟綀?zhí)行
    if (Dep.target) {
        watcher.depend();
      }

第二步:執(zhí)行watcher.get() , 并執(zhí)行函數(shù)體getter: this.getter.call(vm, vm)
執(zhí)行的時(shí)候就會(huì)進(jìn)入到依賴的數(shù)據(jù)的get中,也就是defineReactive$$1 上面執(zhí)行dep.depend(),我們知道this.newDeps搜集了響應(yīng)式數(shù)據(jù)的發(fā)布訂閱器。依賴多少響應(yīng)式數(shù)據(jù)就搜集多少

當(dāng)前的Dep.target(當(dāng)前計(jì)算屬性的watcher)通過(guò)this.newDeps搜集了所有的響應(yīng)式依賴

  get () {
    pushTarget(this);
    let value;
    const vm = this.vm;
    try {
      value = this.getter.call(vm, vm);
    } finally {
      popTarget();
      this.cleanupDeps();
    }
    return value
  }

第三步:執(zhí)行上面的finally,this.cleanupDeps。this.deps拿到了響應(yīng)式依賴

 cleanupDeps () {
    let i = this.deps.length;
    while (i--) {
      const dep = this.deps[i];
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this);
      }
    }
    let tmp = this.depIds;
    this.depIds = this.newDepIds;
    this.newDepIds = tmp;
    this.newDepIds.clear();
    tmp = this.deps;
    this.deps = this.newDeps;
    this.newDeps = tmp;
    this.newDeps.length = 0;
  }

第四步:執(zhí)行完watcher.get。終于來(lái)到重點(diǎn)環(huán)節(jié).現(xiàn)在知道了this.deps的來(lái)源了。現(xiàn)在回到了計(jì)算屬性的get來(lái)執(zhí)行這一段

  if (Dep.target) {
        watcher.depend();
     }

等等,watcher.depend循環(huán)執(zhí)行了所有依賴項(xiàng)的發(fā)布訂閱器Dep.depend()方法。這是什么意思?this.deps里面已經(jīng)是所有的依賴了,再執(zhí)行一遍?

這里有個(gè)巧妙的轉(zhuǎn)變:還記得我們上面說(shuō)的push之后又pop刪掉了,Dep.target會(huì)變化吧。它變?yōu)榱苏l(shuí),我們繼續(xù)看

通過(guò)斷點(diǎn),我發(fā)現(xiàn)targetStack里面還有其他東西。 Dep.target變?yōu)榱?:this.update(),這玩意兒一看就知道是更新用的。

企業(yè)微信截圖_36d70ee9-6e95-44d3-9819-d4322095ca03.png

通過(guò)全局搜索new Watcher才知道:有三類watcher
(1)computed的watcher。默認(rèn)lazy并不馬上執(zhí)行g(shù)et,這個(gè)我們是知道的
(2)自定義watcher,也就是我們自己寫(xiě)的this.$watch,馬上執(zhí)行g(shù)et,這里暫且不論。代碼為證:

Vue.prototype.$watch = function (
    expOrFn,
    cb,
    options
  ) {
    const vm = this;
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {};
    options.user = true;
    const watcher = new Watcher(vm, expOrFn, cb, options);
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`;
      pushTarget();
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
      popTarget();
    }
    return function unwatchFn () {
      watcher.teardown();
    }
  };
}
(3)render watcher 觸發(fā)渲染視圖用的,馬上執(zhí)行g(shù)et。我們看看簡(jiǎn)化代碼mountComponent
function mountComponent (vm,el,hydrating){
   let  updateComponent = () => {
      vm._update(vm._render(), hydrating);
    };
 // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
return vm
}

明白了:初始化的時(shí)候mountComponent,執(zhí)行了new Watcher中的get。在targetStack數(shù)組中,第一個(gè)元素就是會(huì)引發(fā)渲染的 updateComponent的watcher。

所以:當(dāng)計(jì)算屬性的get執(zhí)行到下面代碼時(shí)

Dep.target此時(shí)為render watcher。
// 如果頁(yè)面上沒(méi)有使用計(jì)算屬性,
//但是又通過(guò)this.computed取值觸發(fā)計(jì)算屬性的get
//這里就會(huì)是undefined
  if (Dep.target) {
        watcher.depend();
     }
依次執(zhí)行下面的代碼
class Watcher {
  depend () {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  }
}
此時(shí)Dep.target 就變?yōu)榱藆pdateComponent的watcher。
所以此時(shí)addDep是給render watcher添加依賴
class Dep {
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
 }

1個(gè)Dep可以被多個(gè)Watcher訂閱,1個(gè)Watcher也可以訂閱多個(gè)依賴。通過(guò)遍歷計(jì)算屬性watcher的deps,讓render watcher去訂閱他們: 計(jì)算屬性的值變化了,就會(huì)發(fā)render watcher 的update執(zhí)行以觸發(fā)更新視圖。

總結(jié)一下整個(gè)流程:
  1. 初始化的時(shí)候lazy = dirty = true。將計(jì)算屬性設(shè)置為響應(yīng)式并重寫(xiě)了get

  2. 當(dāng)頁(yè)面上要使用計(jì)算屬性時(shí),會(huì)進(jìn)入到計(jì)算屬性的get,get中dirty = true會(huì)進(jìn)入到watcher.get并執(zhí)行pushTarget(this)。此時(shí)全局的Dep.target就是計(jì)算屬性的watcher

  3. 此時(shí)執(zhí)行計(jì)算屬性的函數(shù)體getter,里面有一個(gè)響應(yīng)式數(shù)據(jù),比如:this.$store.state.name.就會(huì)進(jìn)入到該響應(yīng)式數(shù)據(jù)的get。這個(gè)get就是我們上面說(shuō)的過(guò)程:發(fā)現(xiàn)Dep.target(當(dāng)前watcher)有值,就將此watcher放入了實(shí)例的Dep(訂閱watcher)

  4. 在set的時(shí)候如果值沒(méi)變化,直接return。有變化就利用dep. notify通知watcher執(zhí)行update 把dirty設(shè)置為true。這里的響應(yīng)式數(shù)據(jù)返回一個(gè)新值

  5. 響應(yīng)式數(shù)據(jù)拿到新值,計(jì)算屬性也就拿到了新值。然后popTarget。 此時(shí)的Dep.target變?yōu)榱藃ender watcher 。然后執(zhí)行watcher.depend(),給render watcher 添加依賴。觸發(fā)變化后,引起試圖更新

尾聲:update更新部分,怎么合并更新? 有時(shí)間再更新
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容