一、前言
數據雙向綁定作為 Vue 核心功能之一,其實現原理主要分為兩部分:
本篇文章主要介紹 Vue 實現數據劫持的思路,下一篇則會介紹發布訂閱模式的設計。
二、針對 Object 類型的劫持
對于 Object 類型,主要劫持其屬性的讀取與設置操作。在 JavaScript 中對象的屬性主要由一個字符串類型的“名稱”以及一個“屬性描述符”組成,屬性描述符包括以下選項:
上述 setter 和 getter 方法就是供開發者自定義屬性的讀取與設置操作,而設置對象屬性的描述符則少不了 Object.defineProperty() 方法:
function defineReactive (obj, key) { let val = obj[key] Object.defineProperty(obj, key, { get () { console.log(' === 收集依賴 === ') console.log(' 當前值為:' + val) return val }, set (newValue) { console.log(' === 通知變更 === ') console.log(' 當前值為:' + newValue) val = newValue } })}const student = { name: 'xiaoming'}defineReactive(student, 'name') // 劫持 name 屬性的讀取和設置操作上述代碼通過 Object.defineProperty() 方法設置屬性的 setter 與 getter 方法,從而達到劫持 student 對象中的 name 屬性的讀取和設置操作的目的。
讀者可以發現,該方法每次只能設置一個屬性,那么就需要遍歷對象來完成其屬性的配置:
Object.keys(student).forEach(key => defineReactive(student, key))
另外還必須是一個具體的屬性,這也非常的致命。
假如后續需要擴展該對象,那么就必須手動為新屬性設置 setter 和 getter 方法,**這就是為什么不在 data 中聲明的屬性無法自動擁有雙向綁定效果的原因 **。(這時需要調用 Vue.set() 手動設置)
以上便是對象劫持的核心實現,但是還有以下重要的細節需要注意:
1、屬性描述符 - configurable
在 JavaScript 中,對象通過字面量創建時,其屬性描述符默認如下:
const foo = { name: '123'}Object.getOwnPropertyDescriptor(foo, 'name') // { value: '123', writable: true, enumerable: true, configurable: true }前面也提到了 configurable 的值如果為 false,則無法再修改該屬性的描述符,所以在設置 setter 和 getter 方法時,需要注意 configurable 選項的取值,否則在使用 Object.defineProperty() 方法時會拋出異常:
// 部分重復代碼 這里就不再羅列了。function defineReactive (obj, key) { // ... const desc = Object.getOwnPropertyDescriptor(obj, key) if (desc && desc.configurable === false) { return } // ...}
新聞熱點
疑難解答
圖片精選