淺究Vue響應式原理

Miloo發表於2017-12-08

淺究Vue響應式原理
攝於2017年11月24日 09:02:42 By OnePlusA5000 in HangZhou

前言(概念)

Vue最明顯的特性之一便是它的響應式系統,其資料模型即是普通的 JavaScript 物件。而當你讀取或寫入它們時,檢視便會進行響應操作。文章簡要闡述下其實現原理,如有錯誤,還請不吝指正。個人部落格地址:hiybm.cn

響應式data
<div id = "exp">{{ message }}</div>

const vm = new Vue({
	el: '#exp',
	data: {
		message: 'This is A'
	}
})
vm.message = 'This is B'	// 響應式
vm._message = 'This is C'	// 非響應式
複製程式碼

上述程式碼中,data是Vue例項的資料物件,當例項初始化時,Vue 會遍歷 data 中的所有屬性,並且使用 Object.definePropery 把這些屬性全都轉為 getter/setter ,從而讓 data 的屬效能夠響應資料變化。另外,Object.defineProperty 是 ES5 中一個無法 shim(墊片) 的特性,這也就是為什麼 Vue 不支援 IE8 以及更低版本瀏覽器的原因。物件必須是純粹的物件 (含有零個或多個的 key/value 鍵值對):瀏覽器 API 建立的原生物件。所以,在data中宣告過的message是響應式資料,而由於_message是在data外使用 Vue 例項增加的資料,所以亦不屬於響應式。

關於Object.definePropery

Object.defineProperty() 方法會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性,並返回這個物件。這個API是實現響應式資料的關鍵所在

Syntax: Object.defineProperty(obj, prop, descriptor)

  • obj: 要定義屬性的物件
  • prop: 要定義或修改的屬性的名稱
  • descriptor: 將被定義或修改的屬性描述符。

Tips: 要知道ECMAScript中有兩種屬性:資料屬性和訪問器屬性。這裡的descriptor可取值有資料屬性和訪問器屬性。 資料屬性: 包含一個資料值的位置,在此位置可以進行讀寫操作,有以下特性:

  • [[Configurable]]:對屬性的操作可配置性開關,如刪除,修改。預設值為true
  • [[Enumberble]]:是否可列舉(通過for-in)。預設值為true
  • [[Writable]]:能否修改屬性的值。預設值為true
  • [[value]]:包含這個屬性的資料值,讀取時從該位置讀,寫入時把新值存到該位置。預設值為undefined

訪問器屬性: 不包含資料值,包含一個函式對(getter/setter)。特性如下:

  • [[Configurable]]:對屬性的操作可配置性開關,如刪除,修改。預設值為true
  • [[Enumberble]]:是否可列舉(通過for-in)。預設值為true
  • [[Get]]:讀取屬性時呼叫的函式。預設值為undefined
  • [[Set]]:寫入屬性時呼叫的函式。預設值為undefined

Tips: 在讀取訪問器屬性時,就會呼叫getter函式,該函式負責返回有效的值;在寫入訪問器屬性時,會呼叫setter函式並傳入新值,該函式負責決定如何處理資料,但是這兩個函式不一定非要同時存在。Vue便是利用getter/setter這一特性來實現的響應系統。 示例程式碼:

// 定義一個book物件,_year和edition都屬於資料屬性。
var book = {
    _year : 2004,
    edition : 1
};
// 對book物件建立 year 訪問器屬性。
Object.defineProperty(book, "year",{ 
    // 讀取 year 訪問器屬性時,get() 方法返回 _year 的值。
    get : function () {
        console.info(this._year, 'get');   // 2004
        return this._year;
    },
    // 寫入 year 訪問器屬性時,set() 方法對新值進行操作。
    set : function (newValue) {
        if (newValue > 2004) {
            this._year = newValue;
            console.info(this._year, 'set')   // 2005   
            this.edition += newValue - 2004;
        }
    }
});
// 讀取 year 訪問器屬性時會返回_year的值。
book.year;
// 寫入 year 訪問器屬性時會呼叫set() 函式,進行操作。
book.year = 2005;   
console.info(book.edition)  // 2
console.info(book) // 此處藏有彩蛋。
複製程式碼
watcher

官方表述:每個元件例項都有相應的 watcher 例項物件,它會在元件渲染的過程中把屬性記錄為依賴,之後當依賴項的 setter 被呼叫時,會通知 watcher 重新計算,從而致使它關聯的元件得以更新。

如下圖所示:

淺究Vue響應式原理
Tips:中每個指令/資料繫結都有一個對應的 watcher 物件。其中 watcher扮演的角色相當於是一個紐帶,這個紐帶的作用就是依賴收集。

END

文中還有部分深層細節沒有講述到,後續我也會接著更新系列文章來進一步深深深究vue底層的響應式原理,SYNT。

參考連結

Object.defineproperty

深入響應式原理

深入淺出之vue響應式原理

Javascript高階程式設計(第3版)6.1章節

相關文章