博客 / 詳情

返回

vue源碼解析系列-compute實現機制

本來vue的響應式應該才是重中之重。但是網上的文章很多很多。在看computed的實現之前。肯定還是要把vue的響應式如何實現好好看一下。或者説兩者根本就是一樣的東西。這邊推薦幾篇文章關於vue的響應式。

vue響應式簡單實現

vue慕課響應式手記

還是看看官網對於響應式的解釋:
圖片描述

總的來説。vue實現響應式的關鍵有三個:watcher,dep,observe;

  1. observe:遍歷data中的屬性。在get,set方法中設置核心數據劫持

  2. dep:每個屬性都有一個自己的dep(消息訂閲起)用於訂製該屬性上的所有觀察者

  3. watcher:觀察者,通過dep實現對響應屬性的監聽觀察。觀察得到結果後,主動觸發自己的回調

可以去看看vue2.3的這三部分源碼。中間還是有很多精美的設計。比如一個全局唯一的Dep.target,在任何時候都是唯一的值。以確保同一時間只有一個觀察者在訂閲。再比如,watcher中也會存下相關的訂閲器,實現去重和實現同一個觀察者的分組(這裏是實現computed的關鍵),再如。watcher中的id也會唯一。用於異步更新的時候不同時出發相同的訂閲。仔細看看會收穫不小。改天我把所有的響應式的代碼也整理一下。
在理解了響應式的情況下。我們來看看computed的實現。最簡單的一個demo如下:


<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="app">
  <div name="test">{{computeA}}</div>

</div>
</body>
<script src="vue.js"></script>
<script type="text/javascript">
  new Vue({
  el: '#app',
  data: function () {
    return {
      firstName: 111,
      lastName: 222
    }
  },
  computed: {
    computeA: function () {
      return this.firstName + ' ' + this.lastName
    }
  },
  created(){
    setTimeout(
      () => {
        this.firstName = 333;
      },1000
    )
  }
})
</script>
</html>

我們來從源碼的角度看看發生了什麼:

  1. 在初始化實例創建響應式的時候。對options中的computed做了特殊處理:

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;
    {
      if (getter === undefined) {
        warn(
          ("No getter function has been defined for computed property \"" + key + "\"."),
          vm
        );
        getter = noop;
      }
    }
    // create internal watcher for the computed property.
    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);//為每一個computed項目訂製一個watcher

    // 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);
      }
    }
  

function defineComputed (target, key, userDef) {
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = createComputedGetter(key);
    sharedPropertyDefinition.set = noop;
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop;
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop;
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

function createComputedGetter (key) {//構造該computed的get函數
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate();//收集該watcher的訂閲
      }
      if (Dep.target) {
        watcher.depend();//同一為這一組訂閲再加上組件re-render的訂閲(該訂閲負責更新組件)
      }
      return watcher.value
    }
  }
}

總的來説。理解了響應式的構建之後。再來看computed的實現還是很直觀的。組件初始化的時候。computed項和data中的分別建立響應式。data中的數據直接對屬性的get,set做數據攔截。而computed則建立一個新的watcher,在組件渲染的時候。先touch一下這個computed的getter函數。將這個watcher訂閲起來。這裏相當於這個computed的watcher訂閲了firstname和lastname。touch完後。Dep.target此時又變為之前那個用於更新組件的。再通過watcher.depend()將這個組統一加上這個訂閲。這樣一旦firstname和lastname變了。同時會觸發兩個訂閲更新。其中一個便是更新組件。重新re-render的函數。感覺看的還不夠細啊

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.