vue.js數據響應式原理解析
Object.defineProperty()
得力於 Object.defineProperty() 的特性,vue 的數據變化有別於 react 和小程序,是非侵入式的。詳細介紹可以看 MDN 文檔,這裡特別說明幾點:
- get / set 屬性是函數,出於習慣會被稱為 getter 函數 / setter 函數(Java,c++ 中都有這種慣例)
value 或 writable
和get 或 set
是不能同時出現的,否則報錯- 註意區別 Object.defineProperties()
定義 defineReactive 函數
Object.defineProperty()
在使用 getter 和 setter 的時候,要想實現屬性的修改,需要借助一個變量周轉,如下面的 value,這就很麻煩。
const obj = {} let value Object.defineProperty(obj, 'a', { enumerable: true, configurable: true, get() { console.log('getter') return value }, set(newValue) { value = newValue console.log('setter', newValue) } })
所以定義瞭 defineReactive 函數,方便去給對象增加一個響應式屬性。這裡創建一個閉包的環境:閉包一定要有內外兩個函數,外面這個函數 defineReactive 的 value 就形成瞭閉包。
const obj = {} function defineReactive(data, key, value) { // 如果隻傳瞭兩個參數,則讓 value 直接等於 data[key] if (arguments.length === 2) value = data[key] Object.defineProperty(data, key, { enumerable: true, // 可被枚舉(for...in 或 Object.keys 方法) configurable: true, // 可被配置,比如刪除 get() { console.log('查看瞭' + key + '屬性') return value }, set(newValue) { console.log('修改瞭' + key + '屬性') value = newValue } }) } defineReactive(obj, 'a', 10) console.log(obj.a) obj.a = 11 console.log(obj.a)
得到的結果如下圖:
遞歸偵測對象的全部屬性
我們自己寫一個能夠偵測對象全部屬性的庫
新建 index.js 作為主入口文件,用於測試效果,我們 let 一個對象 obj,目標是通過把 obj 作為參數傳給 observe 函數,即可實現對 obj 對象所有屬性的查看與修改的監測。
// index.js import observe from './observe.js' let obj = { a: { m: { n: 1 } }, b: 2 } observe(obj)
流程分析
observe 函數
observe 函數用於觀察一個對象(value)的屬性是否已被監測的(是否有 __ob__
屬性),如果不是則讓其屬性成為響應式的(通過 new Observer(value)
)。
註意:之所以起瞭 __ob__
這麼奇怪的變量名,是為瞭保證不會與對象的原有屬性同名。
// observe.js import Observer from './Observer.js' export default (value) => { if (typeof value !== 'object') return if (value.__ob__ !== undefined) { // 暫時留空 } else { new Observer(value) } }
Observer 類
Observer 是一個類,一旦 new 瞭一個實例,則做 2 件事:
- 給傳入的 value(其實是個對象) 添加
__ob__
屬性,值為這次 new 的實例(也就是構造函數中的 this),因為希望__ob__
屬性是不可被枚舉的,所以用 def 函數處理。 - 遍歷 value 的屬性,通過 defineReactive 函數將其變為響應式的
// Observer.js import { def } from './utils.js' import defineReactive from './defineReactive.js' export default class Observer { constructor(value) { def(value, '__ob__', this, false) this.walk(value) } // 處理對象,讓對象的屬性變為響應式 walk(value) { for (let key in value) { defineReactive(value, key) } } }
def 函數定義如下:
export const def = (obj, key, value, enumerable) => { Object.defineProperty(obj, key, { value, enumerable, writable: true, configurable: true }) }
完善 defineReactive 函數
相較於前面定義的時候,在兩個地方添加瞭 observe(value),從而實現瞭遞歸偵測對象的全部屬性。這裡的參數 value,就是已經被變為響應式的屬性的值,這個值如果是個對象,也需要被偵測,所以也要被 observe。
// defineReactive.js import observe from './observe.js' export default function defineReactive(data, key, value) { if (arguments.length === 2) value = data[key] // 註意這裡不是傳 key 而是傳 value,因為 key 隻是一個字符串,value 才是 key 指向的對象 observe(value) // 讓 data 的 key 屬性變為響應式屬性 Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { console.log('查看瞭' + key + '屬性') return value }, set(newValue) { console.log('修改瞭' + key + '屬性') value = newValue // 修改的屬性也需要被觀察,如果是對象需要被偵測 observe(newValue) } }) }
至此,在 index.js 傳入 observe 的 obj 的每個屬性都是響應式的瞭
// index.js ...省略前面的代碼 obj.a.m = { y: 8 } console.log(obj.a.m.y)
測試結果如下:
One More Thing
普通對象也是有 getter 和 setter 的:
- get propertyName(){} 用來得到當前屬性值的回調函數
- set propertyName(){} 用來監視當前屬性值變化的回調函數
- 下面的代碼中,屬性 a 稱為“數據屬性”,它隻有一個簡單的值;屬性b這種用 getter 和 setter 方法定義的屬性稱為“存取器屬性”。
var num= { a: 2, get b(){ return 2 } }
存取器屬性定義為一個或兩個與屬性同名的函數,這個函數定義不使用 function 關鍵字,而是使用 get 或 set,也沒有使用冒號將屬性名和函數體分開。
到此這篇關於vue.js數據響應式原理解析的文章就介紹到這瞭,更多相關vue.js響應式原理 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Vue2 Observer實例dep和閉包中dep區別詳解
- 關於vue中如何監聽數組變化
- vue MVVM雙向綁定實例詳解(數據劫持+發佈者-訂閱者模式)
- 淺析vue偵測數據的變化之基本實現
- 手寫Vue源碼之數據劫持示例詳解