Vue 源碼淺析:數據動態響應-觀察者對象 Observer


Vue 源碼淺析:數據動態響應-觀察者對象 Observer

繼續初始化中未完的內容

初始化流程中,還未提到 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) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
/<code>

所有的內容將圍繞構造器 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):

Vue 源碼淺析:數據動態響應-觀察者對象 Observer



通過 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 方法執行後的結果):

Vue 源碼淺析:數據動態響應-觀察者對象 Observer


接下來看 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>

{{ message }}
/<code>

這裡會判斷 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 建立起觀察機制。


分享到:


相關文章: