根據調試工具看Vue源碼之computed(二)

回顧

上回提到, computed————計算屬性的緩存與 Watcher這個類的 dirty屬性有關,那麼此次咱們接着來看下, dirty屬性到底取決於什麼狀況來變化,從而對 computed進行緩存。

依賴收集

切入正題以前,咱們先來看一個問題:若是一個computed的結果是受data屬性下的值影響的,那麼如何去捕獲因某個值變化而引發的computed的變化?答案是:依賴收集javascript

根據上面的斷點,在update函數執行以前,咱們注意到,有個reactiveSetter函數在它以前。咱們點擊右側調用棧中的reactiveSetter,此時有一個函數特別醒目:defineReactive$$1,通過又一次的斷點,咱們發現它在幾處都有調用:前端

  • initRender函數中調用
  • walk函數中調用

在實際斷點調試的時候,咱們很容易能夠知道存在這樣的,同時也是與本文有關的調用順序(從下往上):java

  • defineReactive$$1
  • walk
  • Observer
  • observe
  • initData
  • initState
  • ...

Observer

根據上邊提供的調用順序,咱們重點看一下幾個關鍵的函數:react

observe
/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

光看註釋咱們都能知道,observe函數的做用是:爲某個值建立一個observer實例,隨後將這個observer實例返回,在這裏起到一個對值進行篩選的做用數組

Observer
/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
var Observer = function Observer (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);
  }
};

註釋大意:緩存

每一個被觀察的對象都附屬於 Observer類。每次對對象的觀察都會將它的 gettersetter屬性覆蓋,用以收集依賴以及觸發更新
walk && defineReactive$$1
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj, keys[i]);
  }
};

/**
 * Define a reactive property on an Object.
 */
function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var 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) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && 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();
    }
  });
}

其中,這端代碼是關鍵:微信

get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },

若是閱讀了整段defineReactive$$1函數,那麼很容易就發現,dep不過是Depnew出來的實例,那麼即便不看Dep.prototype.depend的實現,你也知道dep.depend()其實也就是在收集依賴。
另外,這段代碼意味着單單在data屬性下聲明一個變量是不會進行依賴收集的,須要變量在程序中被調用,那麼纔會被收集到依賴中(其實這也是一種優化)async

Dep類下的相關實現
/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};

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

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  if (process.env.NODE_ENV !== 'production' && !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(function (a, b) { return a.id - b.id; });
  }
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

總結

上面說了這麼多未免有點亂,最後從新梳理下computed實現緩存的思路:函數

  1. Vue在初始化data屬性時,會將data屬性下相關的變量進行觀察observe),同時從新設置它的gettersetter屬性,以便在其被調用時收集到它的依賴。
  2. 初始化computed
  3. 調用computed時判斷this.dirty屬性,爲true時調用evaluate從新計算它的值並將this.dirty置爲false,將值存在this.value 上,再調用computed則直接返回this.value
  4. computed中依賴的值發生變化時會自動觸發該值的setter屬性,緊接着調用notify函數,遍歷一個subs數組,觸發update函數將this.dirty重置爲true
  5. computed再次被調用時,因爲this.dirty已是true,則會從新計算

掃描下方的二維碼或搜索「tony老師的前端補習班」關注個人微信公衆號,那麼就能夠第一時間收到個人最新文章。
優化

相關文章
相關標籤/搜索