本文是針對
[email protected]
進行分析
observer
是Vue核心中最重要的一個模塊(個人認爲),能夠實現視圖與數據的響應式更新,底層全憑observer
的支持。
observer
模塊在Vue項目中的代碼位置是src/core/observer
,模塊共分爲這幾個部分:
Observer
: 數據的觀察者,讓數據對象的讀寫操作都處於自己的監管之下Watcher
: 數據的訂閱者,數據的變化會通知到Watcher
,然後由Watcher
進行相應的操作,例如更新視圖Dep
:Observer
與Watcher
的紐帶,當數據變化時,會被Observer
觀察到,然後由Dep
通知到Watcher
示意圖如下:
Observer
Observer
類定義在src/core/observer/index.js
中,先來看一下Observer
的構造函數
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
value
是需要被觀察的數據對象,在構造函數中,會給value
增加__ob__
屬性,作爲數據已經被Observer
觀察的標誌。如果value
是數組,就使用observeArray
遍歷value
,對value
中每一個元素調用observe
分別進行觀察。如果value
是對象,則使用walk
遍歷value
上每個key,對每個key調用defineReactive
來獲得該key的set/get
控制權。
解釋下上面用到的幾個函數的功能:
observeArray
: 遍歷數組,對數組的每個元素調用observe
observe
: 檢查對象上是否有__ob__
屬性,如果存在,則表明該對象已經處於Observer
的觀察中,如果不存在,則new Observer
來觀察對象(其實還有一些判斷邏輯,爲了便於理解就不贅述了)walk
: 遍歷對象的每個key,對對象上每個key的數據調用defineReactive
defineReactive
: 通過Object.defineProperty
設置對象的key屬性,使得能夠捕獲到該屬性值的set/get
動作。一般是由Watcher
的實例對象進行get
操作,此時Watcher
的實例對象將被自動添加到Dep
實例的依賴數組中,在外部操作觸發了set
時,將通過Dep
實例的notify
來通知所有依賴的watcher
進行更新。
如果不太理解上面的文字描述可以看一下圖:
Dep
Dep
是Observer
與Watcher
之間的紐帶,也可以認爲Dep
是服務於Observer
的訂閱系統。Watcher
訂閱某個Observer
的Dep
,當Observer
觀察的數據發生變化時,通過Dep
通知各個已經訂閱的Watcher
。
Dep
提供了幾個接口:
addSub
: 接收的參數爲Watcher
實例,並把Watcher
實例存入記錄依賴的數組中removeSub
: 與addSub
對應,作用是將Watcher
實例從記錄依賴的數組中移除depend
:Dep.target
上存放這當前需要操作的Watcher
實例,調用depend
會調用該Watcher
實例的addDep
方法,addDep
的功能可以看下面對Watcher
的介紹notify
: 通知依賴數組中所有的watcher
進行更新操作
Watcher
Watcher
是用來訂閱數據的變化的並執行相應操作(例如更新視圖)的。Watcher
的構造器函數定義如下:
constructor (vm, expOrFn, cb, options) {
this.vm = vm
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
參數中,vm
表示組件實例,expOrFn
表示要訂閱的數據字段(字符串表示,例如a.b.c
)或是一個要執行的函數,cb
表示watcher運行後的回調函數,options
是選項對象,包含deep
、user
、lazy
等配置。
watcher
實例上有這些方法:
get
: 將Dep.target
設置爲當前watcher
實例,在內部調用this.getter
,如果此時某個被Observer
觀察的數據對象被取值了,那麼當前watcher
實例將會自動訂閱數據對象的Dep
實例addDep
: 接收參數dep
(Dep實例),讓當前watcher
訂閱dep
cleanupDeps
: 清除newDepIds
和newDep
上記錄的對dep的訂閱信息update
: 立刻運行watcher
或者將watcher
加入隊列中等待統一flushrun
: 運行watcher
,調用this.get()
求值,然後觸發回調evaluate
: 調用this.get()
求值depend
: 遍歷this.deps
,讓當前watcher
實例訂閱所有dep
teardown
: 去除當前watcher
實例所有的訂閱
Array methods
在src/core/observer/array.js
中,Vue框架對數組的push
、pop
、shift
、unshift
、sort
、splice
、reverse
方法進行了改造,在調用數組的這些方法時,自動觸發dep.notify()
,解決了調用這些函數改變數組後無法觸發更新的問題。在Vue的官方文檔中對這個也有說明:http://cn.vuejs.org/v2/guide/list.html#變異方法
本文轉自https://segmentfault.com/a/1190000008377887