淺析Vue響應式原理(三)
Vue響應式原理之defineReactive
defineReactive
不論如何,最終響應式資料都要通過defineReactive
來實現,實際要藉助ES5新增的Object.defineProperty
。
defineReactive
接受五個引數。obj
是要新增響應式資料的物件;key
是屬性名,val
是屬性名對應的取值;customSetter
是使用者自定義的setter;會在響應式資料的setter中執行,只有開發環境可用;通過shallow
指定是否淺比較,預設深比較。
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } const getter = property && property.get if (!getter && arguments.length === 2) { val = obj[key] } const setter = property && property.set let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
在函式內,首先例項化一個Dep例項dep
,dep
會在稍後新增為響應式資料自定義的get/set中發揮作用。接著獲取屬性描述符,如果屬性不可配置,則無法呼叫Object.defineProperty
來修改setter/getter,所以返回。
如果原來已設定過setter/getter,快取起來。當未自定義getter且arguments
長度為2(即只傳入了obj
和key
)時,可以直接用方括號求值,使用閉包變數val
快取初始值。
如果不是淺複製,執行observe(val)
,為val新增__ob__
屬性並返回__ob__
指向的Observer例項。(只有陣列和物件才可能是響應式,才能返回Observer例項)。
使用Object.defineProperty
為obj[key]
設定getter和setter。
在get
內,如果原來已設定過getter,則用快取的getter求值,否則使用閉包變數val
作為返回值;同時新增依賴。此處為兩個Dep例項新增依賴。dep
是閉包變數,在getter/setter中會使用到。另一個Dep例項是childOb.dep
,只用呼叫set/delete
更新響應式資料時,才會觸發;如果value
是陣列,還會遍歷元素,為存在__ob__
屬性的元素收集依賴。
在set
內,先獲取更新前的值(邏輯和get
內第一步一樣)。判斷更新前後的值是否相等,相等時直接返回;不等時,如果有快取的setter,呼叫快取的setter更新,否則直接賦值。值得注意的是,NaN === NaN
NaN !== NaN
是成立的,後面的判斷語句newVal !== newVal && value !== value
就是為了避免newVal/val
都是NaN
。在更新後的值newVal
上執行observe
,更新閉包變數childOb
,並呼叫notify。