繼續初始化中未完的內容
初始化流程中,還未提到 initState,這個方法涉及 vue 中有關數據狀態的一些操作:
<code>export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}/<code>
initState 方法內涉及 Vue 主要幾個 options 屬性的初始化,他們具備數據動態響應特性,我們大致看下他們的方法的內部結構:
initProps
<code>function initProps (vm: Component, propsOptions: Object) {
//...
defineReactive(props, key, value)
}/<code>
initData
<code>function initData (vm: Component) {
//...
observe(data, true /* asRootData */)
}/<code>
initComputed
<code>function initComputed (vm: Component, computed: Object) {
//...
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}/<code>
initWatch
<code>function initWatch (vm: Component, watch: Object) {
//...
createWatcher(vm, key, handler[i])
}/<code>
注意,沒有貼出 initMethods 方法,因為如果它和 props 重名,會覆蓋掉 props 的定義。所以它不具備數據響應功能。只是因為流程關係,放在了 props 之後。
下面開始 Vue 裡的數據響應說明。
從 initData 開始,observe 觀察對象
因為 data 是我們用的最多的屬性,同時所以的數據都在此定義,initData 也是 vue 初始化狀態中最代表性的方法,懂了它,其他的初始化方法也會很快理解。
先是做一系列和 props、methods 的校驗 warn 判斷,最後執行 observe 方法:
<code>function initData (vm: Component) {
//...
observe(data, true /* asRootData */)
}/<code>
observe 翻譯為:觀察。此處它的入參是 data,所以會觀察 data 對象。也是整個數據響應的入口。
所以抱著這個思路來看後續的代碼:
<code>export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
//...
return ob
}/<code>
此方法將獲得 ob 對象,此對象將從 Observer 觀察者中創建獲取 or 從當前 value 中的 _ ob_ 屬性中獲取。
注意,只有數組,對象等複雜類型數據才會被創建觀察者對象。要是像普通的 Number、String 將不會被觀察。
觀察者 Observer
下面是 Observer 類方法的代碼:
<code>export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array) { /<code>
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
所有的內容將圍繞構造器 constructor 開始。
通過 def 方法,定義 _ob_ 屬性
構造器中會通過 def 方法來定義 _ob_ 屬性:
<code>constructor (value: any) {
def(value, '__ob__', this)
}/<code>
def 方法中,將見到 vue 所用"活"的 defineProperty 方法:
<code>export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}/<code>
通過
defineProperty 原生方法,往當前 value 對象(data)新添加 _ ob_ 屬性,並且設置相關的對象屬性描述特徵。解釋下 def 的傳入參數:obj 是我們 vm.$options 中設置的 data 對象,key 為 _ ob_ 屬性,val 為 Observer 的 this 引用(屬性包括:value,dep,vmCount):
通過 walk 方法,遍歷 data 屬性
之後,構造器最後通過 walk 方法,將 vm.$options.data 上所有的對象屬性統統執行 defineReactive 方法:
<code>constructor (value: any) {
//...
this.walk(value)
}/<code>
<code>walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}/<code>
通過 defineReactive 對 data 屬性賦予動態響應能力
因為涉及 Dep 模塊,先大概瞭解此方法的簡化結構:
<code>export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
//...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
//...
},
set: function reactiveSetter (newVal) {
//...
}
})
}/<code>
傳入 defineReactive 的 obj 對象會是這個樣子(這也是 def 方法執行後的結果):
接下來看 defineReactive 內部的邏輯。
創建 Dep 對象
在這裡會創建一個新的 Dep 依賴訂閱對象:
<code>const dep = new Dep()/<code>
初始化已定義的 getter/setter
獲取 data 對象上屬性的具體描述說明,並且取得 getter/setter 訪問器屬性:
<code>const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property && property.get
const setter = property && property.set/<code>
注意,會根據 arguments 參數預先執行 setter 方法:
<code>if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}/<code>
然後會重新對 data 對象上的屬性來定義 getter/setter 訪問器屬性:
<code>let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
//...
},
set: function reactiveSetter (newVal) {
//...
}
})/<code>
reactiveGetter
先看 getter 的定義:
<code>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
}
//...
}/<code>
當模板解析後,就會調用其中設置的變量(如下,message),從而觸發 get 方法:
<code>/<code>
{{ message }}
這裡會判斷 Dep.target ,是否有監聽對象 Watcher。
<code>if (Dep.target) {
//...
}/<code>
具體哪裡對 Dep.target 進行了賦值,見下篇中的:依賴訂閱器 Dep > 哪裡設置 Dep target
我們先假設 Dep.target 存在,那麼將執行 dep.depend 方法;如果子屬性可以被觀察,也將進行依賴解析:
<code>if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
//..
}
}/<code>
到 Dep 模塊具體再看 depend 的作用。
reactiveSetter
setter 相對簡單,主要流程如下:
- 當對 data 中的屬性更新時,先觸發上面的 getter 方法。
- 然後判斷設置的 newVal 和之前的 value 有無不同,如果未發生改變則直接 return。
- 如果有定義的 setter 方法,則會執行直接更新 data 對象;反之,對 val 賦予新值。
- 最後對 newVal 進行 observe 觀察,並通知 dep.notify 。
<code>set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}/<code>
總結
上面是執行 observe 觀察方法後,執行的相關過程,通過 Observer 方法類創建觀察者對象,每個觀察者都具備 __ob__屬性。
接著通過 defineReactive 對 options.data 上的屬性進行數據動態響應的設置,其核心就是通過 getter/setter 建立起觀察機制。
閱讀更多 前端雨爸 的文章