本系列一共七章,Github 地址請查閱這裏,原文地址請查閱這裏。javascript
這是編寫 JavaScript 框架系列第四章。本章將會闡述髒檢查和數據存取器綁定技術,並指出他們的優缺點。java
數據綁定是一個通用的技術,用來綁定來自提供者和消費者的數據源並同步它們。git
這是一個通用定義,歸納了數據綁定技術的通用構建模塊github
handler()
。以上的步驟在不一樣的數據綁定技術中會以不一樣的方式實現。接下來將會介紹兩種技術,即髒檢查和存取器方法。他們都有優缺點,我將在介紹後簡要討論。數組
髒檢查是最爲人熟知的數據綁定方法。它的概念很簡單,不須要複雜的語言特性,這使得它能夠做爲一個很好的候選缺省選擇。bash
定義提供者和消費者不要求任何特別的語法,只須要一個簡單的 JavaScript 對象。框架
const provider = {
message: 'Hello World'
}
const consumer = document.createElement('p')
複製代碼
同步一般是提供者上的屬性變化觸發。須要監聽變化的屬性,必須明確映射到各自的 handler()
函數。異步
observe(provider, 'message', message => {
consumer.innerHTML = message
})
複製代碼
observe()
函數只保存 (provider, property) -> handler
映射供之後使用。ide
function observe(provider, prop, handler) {
provider._handlers[prop] = handler
}
複製代碼
這樣,咱們就有一個定義提供者和消費者的語法,以及一種爲屬性改變而註冊 handler()
函數的方法。咱們庫的公共 API 已經準備好了,如今介紹其內部實現。函數
髒檢查因一個緣由而被稱爲髒。它定時檢查而不是直接監聽屬性變化。從如今起,咱們把這個檢查稱爲摘要週期。一個摘要週期遍歷每個由 observe()
函數添加的 (provider, property) -> handler
入口,而且檢查自上次遍歷以來屬性值是否發生變化。若是變化則運行 handler()
函數。一個簡單的實現相似以下:
function digest() {
providers.forEach(digestProvider)
}
function digestProvider (provider) {
for (let prop in provider._handlers) {
if (provider._prevValues[prop] !== provider[prop]) {
provider._prevValues[prop] = provider[prop]
handler(provider[prop])
}
}
}
複製代碼
digest()
函數須要一直不停地運行以確保狀態同步。
存取器技術如今是熱點。由於須要 ES5 getter/ setter 功能而未受普遍支持,可是它因語法優雅而彌補了這個弱點。
定義提供者須要特殊的語法。簡單的提供者對象必須被傳入 observable
函數,這個函數能夠用來把提供者對象轉化爲一個被監聽的對象。
const provider = observable({
greeting: 'Hello',
subject: 'World'
})
const consumer = document.createElement('p')
複製代碼
簡單的 handler()
映射語法彌補了這種小的不便。使用髒檢查,咱們不得不以下顯式地定義每一個被監聽的屬性。
observe(provider, 'greeting', greeting => {
consumer.innerHTML = greeting + ' ' + provider.subject
})
observe(provider, 'subject', subject => {
consumer.innerHTML = provider.greeting + ' ' + subject
})
複製代碼
這顯得很囉嗦和笨拙。訪問器技術能夠自動檢測在 handler()
函數中使用過的提供者屬性,這樣容許咱們簡化以上代碼。
observe(() => {
consumer.innerHTML = provider.greeting + ' ' + provider.subject
})
複製代碼
observe()
的實現和髒檢查的不一樣。它只是執行了傳入的 handler()
函數,而且當handler()
運行的時候把函數標識爲目前激活狀態。
let activeHandler
function observe(handler) {
activeHandler = handler
handler()
activeHandler = undefined
}
複製代碼
注意,咱們如今利用了 JavaScript 的單線程特性,使用惟一的 activeHandler
變量來記錄目前運行的 handler()
的函數。
這就是存取器技術名字的由來。提供者使用 getters/setters 來擴展,這兩個方法在後臺進行復雜的工做。思路是以以下方式攔截提供者屬性的存取操做。
activeHandler
在運行,保存 (provider, property) -> activeHandler
映射以備後用。(provide, property)
映射的 handler()
函數。如下代碼展現了一個提供者的屬性變化的簡單的實現過程。
function observableProp(provider, prop) {
const value = provider[prop]
Object.defineProperty(provider, prop, {
get () {
if (activeHandler) {
provider._handlers[prop] = activeHandler
}
return value
},
set (newValue) {
value = newValue
const handler = obj._handlers[prop]
if (handler) {
activeHandler = handler
handler()
activeHandler = undefined
}
}
})
}
複製代碼
在前面一節提到的 observable()
函數遞歸遍歷提供者的屬性,而且使用 observableProp()
函數來轉化全部屬性爲可監測。
function observable (provider) {
for (let prop in provider) {
observableProp(provider, prop)
if (typeof provider[prop] === 'object') {
observable(provider[prop])
}
}
}
複製代碼
這是一個很是簡單的實現,可是對於比較這兩種技術已經足夠了。
本節將會歸納性地指出髒檢查和存取器技術的優缺點。
髒檢查不須要語法來定義的提供者和消費者,可是把 handler()
和 (provider, property)
進行映射配對是笨拙和不靈活的。
存取器技術要求用 observable()
來封裝提供者,可是自動 handler()
映射彌補了這個不足。對於使用數據綁定的大型項目來講,這是必須的功能。
髒檢查由於糟糕的性能問題而廣受垢病。它不得不在每一個摘要週期可能屢次檢查每一個 (provider, property) -> handler
入口。並且,由於它沒法知曉何時屬性值發生變化,因此即使應用處於閒置狀態也必須保持運轉。
存取器速度更快,可是若是是監聽一個很大的對象的話會不可避免地下降性能。使用存取器來替換提供者的每一個屬性常常會致使過分濫用。一個方案是在須要的時候動態構建存取器樹而不是一開始就成批地建立。還有一種可替代的簡單方案便是把不須要監聽的屬性用 noObserve()
函數封裝起來,這個能夠告訴 observable()
不要處理這些屬性。使人沮喪的是,這會引入一些額外的語法。
髒檢查天生支持 expando(動態添加)和存取器屬性。
存取器技術在這裏有一個弱點。由於 Expando 屬性不在初始的存取樹上面,因此不支持存取器技術。舉個栗子,這會致使數組問題,可是能夠在添加了一個屬性以後經過手動運行 observableProp()
來解決。由於存取器不能被存取器二次封裝,因此存取屬性是不支持的。一般的解決方法是使用 computed()
函數而不是 getter。這將引入更多的自定義語法。
髒檢查沒有給咱們太多選擇的自由,由於咱們不知道何時屬性值真正發生了改變。handler()
函數只能經過不斷地運行 digest() 循環來異步執行。
存取器技術建立的存取器是同步觸發的,因此咱們能夠自由選擇。能夠選擇立刻運行 handler()
,或者將其保存在稍後異步執行的批處理中。前一種技術給予咱們可預測性的優勢,然後者能夠經過移除重複項來提高性能。