【翻譯】Vue.js源碼分析:計算屬性如何工做

原文 Vue.js Internals: How computed properties workjavascript

這篇文章咱們我會用很簡單的方法來實現相似計算屬性的效果,以此學習Vue.js的計算屬性的運行機制。html

  1. 這個例子只說明運行機制,不支持對象、數組、watching/unwatching等Vue.js已實現的一大堆優化vue

  2. 看完源代碼帶着我有限的理解寫的這篇文章,可能會有一些錯誤,如發現錯誤,請聯繫我java

JS的屬性

JS有Object.defineProperty方法,它能作的事情不少,但咱們先關注這一點:react

var person = {};

Object.defineProperty (person, 'age', {
  get: function () {
    console.log ("Getting the age");
    return 25;
  }
});

console.log ("The age is ", person.age);

// 輸出:
//
// Getting the age
// The age is 25

雖然看起來咱們只是訪問了對象的一個屬性,可是其實咱們是在運行一個函數。數組

基礎的Vue.js Observable

Vue.js有一個基礎結構,它能夠幫你把一個常規的對象轉換成一個「被觀察」的值,這個值就叫作「observable」。如下是一個實現響應式屬性的例子緩存

function defineReactive (obj, key, val) {
  Object.defineProperty (obj, key, {
    get: function () {
      return val;
    },
    set: function (newValue) {
      val = newValue;
    }
  })
};

// 建立對象
var person = {};

// 添加兩個響應式屬性
defineReactive (person, 'age', 25);
defineReactive (person, 'country', 'Brazil');

// 如今你可使用這兩個屬性
if (person.age < 18) {
  return 'minor';
}
else {
  return 'adult';
}

// 也能夠給他們賦值
person.country = 'Russia';

有趣的是,25Brazil還是閉包變量val,當你賦值時,它們的值也會被修改。這個值不是存在於person.country,而是存在於getter函數閉包。閉包

定義一個計算屬性

建立一個計算屬性函數defineComputed函數

defineComputed (
  person, // the object to create computed property on
  'status', // the name of the computed property
  function () { // the function which actually computes the property
    console.log ("status getter called")
    if (person.age < 18) {
      return 'minor';
    }
    else {
      return 'adult';
    }
  },
  function (newValue) {
    // called when the computed value is updated
    console.log ("status has changed to", newValue)
  }
});

// We can use the computed property like a regular property
console.log ("The person's status is: ", person.status);

如下是defineComputed的簡單實現。這個函數支持調用計算函數,可是暫不支持updateCallback學習

function defineComputed (obj, key, computeFunc, updateCallback) {
  Object.defineProperty (obj, key, {
    get: function () {
      // call the compute function and return the value
      return computeFunc ();
    },
    set: function () {
      // don't do anything. can't set computed funcs
    }
  })
}

這個函數有兩個問題

  1. 每當屬性被訪問,計算函數都會運行

  2. 這個函數不知道什麼時候應該更新

// 咱們但願達到這個效果

person.age = 17;
// console: status has changed to: minor

person.age = 22;
// console: status has changed to: adult

添加一個依賴跟蹤器

建立一個全局變量Dep

var Dep = {
  target: null
};

這就是依賴跟蹤器,如今咱們把這個關鍵點融入defineComputed

function defineComputed (obj, key, computeFunc, updateCallback) {
  var onDependencyUpdated = function () {
    // TODO
  }
  Object.defineProperty (obj, key, {
    get: function () {
      // Set the dependency target as this function
      Dep.target = onDependencyUpdated;
      var value = computeFunc ();
      Dep.target = null;
    },
    set: function () {
      // don't do anything. can't set computed funcs
    }
  })
}

咱們如今回到定義響應式對象的步驟

function defineReactive (obj, key, val) {
  // all computed properties that depend on this
  var deps = [];

  Object.defineProperty (obj, key, {
    get: function () {
      // 檢查是否有計算屬性調用這個getter
      // 順便檢查這個以來是否存在
      if (Dep.target && ) {
        // 添加依賴
        deps.push (target);
      }

      return val;
    },
    set: function (newValue) {
      val = newValue;

      // 通知全部依賴這個值的計算屬性
      deps.forEach ((changeFunction) => {
        // 請求從新計算
        changeFunction ();
      });
    }
  })
};

咱們在計算屬性的定義裏更新onDependencyUpdated函數,用以觸發更新回調函數。

var onDependencyUpdated = function () {
  // compute the value again
  var value = computeFunc ();
  updateCallback (value);
}

綜合上述代碼

重新看回咱們以前定義的計算屬性person.status

person.age = 22;

defineComputed (
  person,
  'status',
  function () {
    // compute function
    if (person.age > 18) {
      return 'adult';
    }
  },
  function (newValue) {
    console.log ("status has changed to", newValue)
  }
});

console.log ("Status is ", person.status);

第一步:

person.status被訪問,觸發了get()函數,Dep.target如今是onDependencyUpdated
clipboard.png

第二步:

(計算屬性的get()函數第二行)調用了計算函數computeFunc,而這個計算函數又調用了age屬性,也就是觸發了age屬性的get()
clipboard.png

第三步:

看回age屬性的get(),若是Dep.target當前是有值的話,這個值就會被push到依賴列表(因此如今onDependencyUpdated就在age屬性的依賴列表裏咯),以後Dep.target會被賦值爲null

clipboard.png

第四步:

如今,每當person.age被賦值,都會通知person.status
clipboard.png


某譯者的胡說八道
如做者所說這個例子只是簡化版,像官網說計算屬性是基於它們的依賴進行緩存的這點沒有表現出來,因此更多細節請研究Vue的源碼
可是讀了這篇文章咱們能夠知道計算屬性更新是依賴data的屬性通知的,因此必須調用了data的屬性纔會「從新計算」,不然永遠不會更新
這就是爲何官網說

clipboard.png

若是計算函數裏面調用了多個屬性,那麼這些屬性更新時都會通知這個計算函數。

其餘參考 Vue 響應式原理探析

相關文章
相關標籤/搜索