vue官方對響應式原理的解釋:深刻響應式原理javascript
總結下官方的描述,大概分爲一下幾點:html
然鵝,官方的介紹只是一個大體的流程,咱們仍是不知道vue究竟是怎樣給data的每一個屬性設置getter、setter方法?對象屬性和數組屬性的實現又有什麼不一樣?怎樣實現依賴的收集和依賴的觸發? 想要搞清楚這些,不得不看一波源碼了。下面,請跟我從vue源碼分析vue的響應式原理vue
--- 下面我要開始個人表演了---java
vue源碼的 instance/init.js 中是初始化的入口,其中初始化分爲下面幾個步驟:react
//初始化生命週期 initLifecycle(vm) //初始化事件 initEvents(vm) //初始化render initRender(vm) //觸發beforeCreate事件 callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props //初始化狀態,!!!此處劃重點!!! initState(vm) initProvide(vm) // resolve provide after data/props //觸發created事件 callHook(vm, 'created')
其中劃重點的 initState() 方法中進行了 props、methods、data、computed以及watcher的初始化。在instance/state.js中能夠看到以下代碼。數組
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options //初始化props if (opts.props) initProps(vm, opts.props) //初始化methods if (opts.methods) initMethods(vm, opts.methods) //初始化data!!!再次劃重點!!! if (opts.data) { initData(vm) } else { //即便沒有data,也要調用observe觀測_data對象 observe(vm._data = {}, true /* asRootData */) } //初始化computed if (opts.computed) initComputed(vm, opts.computed) //初始化watcher if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
劃重點的initData()方法中進行了data的初始化。代碼依舊在instance/state.js中能夠看到。initData()方法代碼以下(刪節版)。瀏覽器
/* 初始化data */ function initData (vm: Component) { //判斷data是不是一個對象 if (!isPlainObject(data)) { ... } //判斷data中的屬性是否和method重名 if (methods && hasOwn(methods, key)) { ... } //判斷data中的屬性是否和props重名 if (props && hasOwn(props, key)) { ... } //將vm中的屬性轉至vm._data中 proxy(vm, `_data`, key) //調用observe觀測data對象 observe(data, true /* asRootData */) }
initData()函數中除了前面一系列對data的判斷以外就是數據的代理和observe方法的調用。其中數據代proxy(vm, `_data`, key)
做用是將vm的屬性代理至vm._data上,例如:緩存
//代碼以下 const per = new VUE({ data:{ name: 'summer', age: 18, } })
當咱們訪問per.name
時,實際上訪問的是per._data.name
而下面一句observe(data, true /* asRootData */)
纔是響應式的開始。app
總結一下初始化過程大概以下圖 dom
observe函數的代碼在observe/index.js,observe是一個工廠函數,用於爲對象生成一個Observe實例。而真正將對象轉化爲響應式對象的是observe工廠函數返回的Observe實例。
Observe構造函數代碼以下(刪減版)。
export class Observer { constructor (value: any) { //對象自己 this.value = value //依賴收集器 this.dep = new Dep() this.vmCount = 0 //爲對象添加__ob__屬性 def(value, '__ob__', this) //若對象是array類型 if (Array.isArray(value)) { ... } else { //若對象是object類型 ... } }
從代碼分析,Observe構造函數作了三件事:
__ob__
屬性,__ob__
中包含value數據對象自己、dep依賴收集器、vmCount。數據通過這個步驟之後的變化以下://原數據 const data = { name: 'summer' } //變化後數據 const data = { name: 'summer', __ob__: { value: data, //data數據自己 dep: new Dep(), //dep依賴收集器 vmCount: 0 } }
當數據是object類型時,調用了一個walk方法,在walk方法中遍歷數據的全部屬性,並調用defineReactive方法。defineReactive方法的代碼仍然在observe/index.js中,刪減版以下:
export function defineReactive (...) { //dep存儲依賴的變量,每一個屬性字段都有一個屬於本身的dep,用於收集屬於該字段的依賴 const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } //緩存原有的get、set方法 const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // 爲每一個屬性建立childOb,而且對每一個屬性進行observe遞歸 let childOb = !shallow && observe(val) //爲屬性加入getter/setter方法 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { ... }, set: function reactiveSetter (newVal) { ... }) }
defineReactive方法主要作了如下幾件事:
__ob__
,其餘屬性的childOb爲undefined)。【經過getter、setter引用】通過defineReactive處理的數據變化以下, 每一個屬性都有本身的dep、childOb、getter、setter,而且每一個object類型的屬性都有__ob__
//原數據 const data = { user: { name: 'summer' }, other: '123' } //處理後數據 const data = { user: { name: 'summer', [name dep,] [name childOb: undefined] name getter,//引用name dep和name childOb name setter,//引用name dep和name childOb __ob__:{data, user, vmCount} }, [user dep,] [user childOb: user.__ob__,] user getter,//引用user dep和user childOb user setter,//引用user dep和user childOb other: '123', [other dep,] [other childOb: undefined,] other getter,//引用other dep和other childOb other setter,//引用other dep和other childOb __ob__:{data, dep, vmCount} }
剛剛講到defineReactive函數的最後一步是每個屬性都加上getter、setter方法。那麼getter和setter函數到底作了什麼呢?
getter函數內部代碼以下:
get: function reactiveGetter () { //調用原屬性的get方法返回值 const value = getter ? getter.call(obj) : val //若是存在須要被收集的依賴 if (Dep.target) { /* 將依賴收集到該屬性的dep中 */ dep.depend() if (childOb) { //每一個對象的obj.__ob__.dep中也收集該依賴 childOb.dep.depend() //若是屬性是array類型,進行dependArray操做 if (Array.isArray(value)) { dependArray(value) } } } return value },
getter方法主要作了兩件事:
__ob__
的依賴收集器__ob__.dep
中,這個依賴收集器在使用$set 或 Vue.set 給屬性對象添加新屬性時觸,也就是說Vue.set 或 Vue.delete 會觸發__ob__.dep
中的依賴。__ob__.dep
中。確保在使用$set 或 Vue.set時,數組中嵌套的對象能正常響應。代碼以下://數據 const data = { user: [ { name: 'summer' } ] } // 頁面顯示 {{user}} <Button @click="addAge()">addAge</Button> //addAge方法,爲數組中的嵌套對象添加age屬性 change2: function(){ this.$set(this.user[0], 'age', 18) }
//dependArray函數 function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] //將依賴收集到每個子對象/數組中 e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } }
//轉化後數據 const data = { user: [ { name: 'summer', __ob__: {user[0], dep, vmCount} } __ob__: {user, dep, vmCount} ] }
dependArray的做用就是將user的依賴收集到它內部的user[0]對象的__ob__.dep
中,使得進行addAge操做時,頁面能夠正常的響應變化。
setter函數內部代碼以下:
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,從新observe childOb = !shallow && observe(newVal) //在set方法中執行依賴器中的全部依賴 dep.notify() } })
setter方法主要作了三件事:
數據是純對象類型的處理講完了,下面看下數據是array類型的操做。
observer/index.js中對array處理的部分:
if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment //攔截修改數組方法 augment(value, arrayMethods, arrayKeys) //遞歸觀測數組中的每個值 this.observeArray(value) }
當數據類型是array類型時
__proto
爲arrayMethods,出於兼容性考慮若是瀏覽器不支持__proto__
,則使用arrayMethods重寫數組數據中的全部相關方法。arrayMethods中的定義在observe/array.js中,代碼以下:
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) //修改數組的方法 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] //攔截修改數組的方法,當修改數組方法被調用時觸發數組中的__ob__.dep中的全部依賴 def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } //對新增元素使用observeArray進行觀測 if (inserted) ob.observeArray(inserted) //觸發__ob__.dep中的全部依賴 ob.dep.notify() return result }) })
在arrayMethods中作了以下幾件事:
__ob__.dep
的全部依賴observeArray代碼以下:
observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }
在observeArray方法,對數組中的全部屬性進行observe遞歸。然而這裏有一個問題就是沒法觀測數組中的全部非Object的基本類型。observe方法的第一句就是
if (!isObject(value) || value instanceof VNode) { return }
也就是說數組中的非Object類型的值是不會被觀測到的,若是有數據:
const data = { arr: [{ test: 0 }, 1, 2], }
此時若是改變arr[0].test=3能夠被觸發響應,而改變arr[1]=4不能觸發響應,由於observeArray觀測數據中的每一項時,observe(arr[0])是一個觀測一個對象能夠被觀測。observe(arr[1])時觀測一個基本類型數據,不能夠被觀測。
響應式階段流程圖
參考文章:揭開數據響應系統的面紗