原文 Vue.js Internals: How computed properties workjavascript
這篇文章咱們我會用很簡單的方法來實現相似計算屬性的效果,以此學習Vue.js的計算屬性的運行機制。html
這個例子只說明運行機制,不支持對象、數組、watching/unwatching等Vue.js已實現的一大堆優化vue
看完源代碼帶着我有限的理解寫的這篇文章,可能會有一些錯誤,如發現錯誤,請聯繫我java
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」。如下是一個實現響應式屬性的例子緩存
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';
有趣的是,25
和Brazil
還是閉包變量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 } }) }
這個函數有兩個問題
每當屬性被訪問,計算函數都會運行
這個函數不知道什麼時候應該更新
// 咱們但願達到這個效果 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
(計算屬性的get()
函數第二行)調用了計算函數computeFunc
,而這個計算函數又調用了age屬性,也就是觸發了age屬性的get()
看回age屬性的get()
,若是Dep.target
當前是有值的話,這個值就會被push到依賴列表(因此如今onDependencyUpdated
就在age屬性的依賴列表裏咯),以後Dep.target
會被賦值爲null
如今,每當person.age
被賦值,都會通知person.status
啦
某譯者的胡說八道
如做者所說這個例子只是簡化版,像官網說計算屬性是基於它們的依賴進行緩存的這點沒有表現出來,因此更多細節請研究Vue的源碼
可是讀了這篇文章咱們能夠知道計算屬性更新是依賴data的屬性通知的,因此必須調用了data的屬性纔會「從新計算」,不然永遠不會更新
這就是爲何官網說
若是計算函數裏面調用了多個屬性,那麼這些屬性更新時都會通知這個計算函數。
其餘參考 Vue 響應式原理探析