--本文采自本人公衆號【猴哥別瞎說】java
這篇文章咱們來詳細瞭解數據響應式的原理與具體實現。react
聊到 Vue 的數據響應,不少人都會對其非侵入式的數據響應系統津津樂道,大概都知道它是經過數據劫持的方式(修改 Object.defineProperty() )來輕量化實現數據響應式。express
所謂輕量化,指的是:Vue 中的數據模型僅僅是一個個普通的 javaScript 對象,當使用者修改它們的時候,對應的頁面 UI 會進行更新。無需直接操做 DOM 元素的同時,對於數據模型的狀態管理也變得簡單明瞭。數組
回顧上篇文章,咱們講到了Watcher: 它與Vue組件實例之間是一一對應的關係。說點題外話:實際上啊,在Vue1.x系列的時候,每個響應式變量都會有一個Watcher。開發者發現這樣的粒度太細了,因而在Vue2.x的時候,就變成了更高粒度的劃分:一個Vue組件實例對應一個Watcher。bash
Vue的數據化響應的具體實現,實際上依賴的還有兩個重要成員:Observer 與 Dep。Observer、Dep、Watcher三者之間的關係在 Vue2.x 中可經過下圖簡單展現:函數
Observer、Dep、Watcher 之間,經過發佈-訂閱模式的方式來進行交互。在數據初始化的時候,Watcher 就會訂閱對應變量的 Dep。當有數據變化的時候,Observer 經過數據劫持的方式,將數據的變動告知 Dep,而 Dep 則會通知有關聯關係的 Watcher 進行數據更新。正如上一節課講到的,Watcher的notify 過程當中調用了 updateComponent,其包含了兩個重要步驟:render 與 update。這兩個步驟最終會更新真實頁面。post
在一個 Vue 組件實例中,Watcher 只有一個。而實例中的每個響應式變量都會有一個 Dep。因而,一個組件中的 Watcher 與 Dep 之間的關係,是一對多的關係。ui
而現實應用中,Vue 組件確定不止一個啊。組件內部還會嵌套組件,而響應式變量有可能會與多個組件產生關聯。因而,在這個層面上,Dep 會對應多個 Watcher。this
綜上,Watcher 與 Dep 之間,是多對多的關係。spa
咱們的目標是:嘗試經過閱讀源碼的方式,將整個知識點串起來。
沿着上節課的引子,咱們能夠在src/core/instance/init.js
文件的initState()
函數中查看:
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)
}
}
複製代碼
initState 中有不少須要初始化的屬性:props/methods/coumputed。咱們此時只關注 data 部分。留意到observe()
方法,進入src/core/observer/index.js
(與數據響應式相關的代碼都是src/core/observer/
內)可知:
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)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
複製代碼
能夠看到,observe()
的做用就是返回一個 Observer 對象。因而重點來了:
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
//值得留意:Observer對象在一個Vue組件實例中存在多個,取決於data數據嵌套了幾個Object對象或數組
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()
方法:
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
複製代碼
這裏就能夠知道,Observer 的做用就是:針對 data 對象的每個屬性,分別對其進行數據響應化處理。值得留意:Observer 對象在一個 Vue 組件實例中存在多個,取決於 data 數據嵌套了幾個 Object 對象或數組。
Observer 是如何與 Dep、Watcher 關聯起來的?咱們先來看看 Dep、Watcher 長啥樣子,而後再來進入到最核心的defineReactive()
。
看看 Dep 的結構吧:
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
複製代碼
從內部變量屬性可知,其包含的靜態變量 target 是一個 Watcher,其包含的常規變量 subs 是 Watcher 數組。其內部主要的兩個方法:depend()
關聯對應的 Watcher,notify()
通知對應的 Watcher 進行 update 操做。
Watcher 的結構以下:
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
}
複製代碼
這裏對 Watcher 的代碼進行了必定的簡化。經過聲明可知:deps
變量是 Dep 數組。其核心方法有這三個:addDeps()
與 Dep 之間相互關聯,get()
調用 updateComponent 方法,update()
執行批量更新操做。
Dep 中的 subs 爲 Watcher 數組,Watcher 中的 deps 爲 Dep 數組。也驗證了以前的描述:
Watcher與Dep之間,是多對多的關係。
複製代碼
此刻,咱們進入到最核心的defineReactive()
:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
//一個key 一個dep(),一一對應
const dep = new Dep()
...//忽略
//val若是是對象或者數組,會遞歸observe
//每個對象或數組的出現,都會出現一個新的Observer
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
//
//值得注意的是,在initState的時候,並無觸發Dep.target,由於尚未Watcher生成,
//Watcher的產生是在第一次$mounted的過程當中生成的
//而之後每次觸發Dep.pushTarget的時候,都會將Dep.target再次被引用到具體的Watcher
//好比:
// watcher.js中的 get()
// state.js中的 getData()
// lifecycle.js中的 callHook()
if (Dep.target) {
//depend()是相互添加引用的過程
//一個Vue實例只有一個Watcher,一個key就有一個Dep
//在單一Vue組件實例中,Watcher與Dep之間,是一對多的關係
//考慮到Vue實例存在嵌套(或用戶手寫了watch表達式),Dep中會保存多個Watcher(存在subs數組中)
//這樣,當key發生變化時,對應的Watcher的notify()方法就會被觸發,對應Vue實例就會更新頁面
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
...//忽略
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
//特別處理:若是最新賦值是對象,該對象仍然須要響應化處理
childOb = !shallow && observe(newVal)
//Dep通知更新
dep.notify()
}
})
}
複製代碼
從代碼可知:
- 每個 data 變量都會有一個 Dep。
- get data 變量的時候,會觸發
dep.depend()
,將 Dep 與 Watcher 之間進行關聯。- set data 變量的時候,會觸發
dep.notify()
,通知 Dep 對應的 Watcher 進行對應的更新操做。
關聯上節課講到的,Watcher 更新的過程會觸發 updateComponent,因而會從新執行$._render()
函數與$._update()
函數,生成虛擬 DOM,進而更新真實 DOM 操做。
因而,這個針對對象的數據響應化過程,就基本走通了。在下一文章中,咱們來看看針對數組的數據響應化過程是怎樣的,它與對象的響應化過程有何不一樣?