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。