概述
由于剛開始學習 vue 源碼,而且水平有限,有理解或表述的不對的地方,還請不吝指教。
vue 主要通過 Watcher、Dep 和 Observer 三個類來實現響應式視圖。另外還有一個 scheduler 來進行調度,本次暫時不做討論。
Watcher 和 Dep 是訂閱者和發布者的關系,每個 Watcher 可以訂閱多個 Dep,而每個 Dep 也可以被多個 Watcher 訂閱。當 Observer 監聽的數據發生改變時,相應的 Dep 就會觸發其訂閱者 Watcher 更新視圖。
下面通過一個簡單的例子來說明其實現流程和原理:
<div id="app">
{{someVar}}
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
someVar: 'init'
},
mounted(){
setTimeout(() => this.someVar = 'changed', 3000)
}
})
</script>
頁面初始會顯示 "init" 字符串,3秒鐘之后,會更新為 "changed" 字符串。
為了便于理解,將整個流程分為三個階段:
- 初始化data,這個階段 vue 通過 Observer 監聽 data 對象,并將普通的 someVar 屬性代理為 get\set 屬性
- 初次掛載el,這個階段 vue 使用默認的 someVar 數據渲染視圖,并將 watcher 添加到 dep 的訂閱者列表
- someVar 更改觸發視圖更新,這個階段 someVar 被賦予了新值,vue 根據 watcher 和 dep 的訂閱關系觸發視圖的更新
下面我們來逐步分析這三個階段的流程。
第一階段
主要流程
new Vue(options) => vm._init(options) => initState(vm) => initData(vm) => observe(data) => new Observer(data) => defineReactive(data, key, value)
說明
初始化操作會監聽 data 對象,對其每一個屬性調用 defineReactive() 方法,將其改造為響應式屬性,代碼如下(去掉了不影響表述主流程的代碼,以便能更清晰的抓住重點):
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any
) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
dep.depend()
return val
},
set: function reactiveSetter (newVal) {
if (newVal === val || (newVal !== newVal && val !== val)) {
return
}
val = newVal
dep.notify()
}
})
}
可以看到,當 get() 方法執行的時候,會調用 dep.depend() ;當 set() 方法執行時,會調用 dep.notify()。后面我們會看到這兩個方法的作用。
第二階段
主要流程
vm.$mount(el) => mountComponent(vm, el) => new Watcher(vm, updateComponent) => watcher.get() => updateComponent() => vm._update(vm._render())
說明
vm._render() 調用 vm.$options.render() 方法生成 vnode,vm._update() 方法根據 vnode 對視圖做更新。
vm.$options.render() 方法是在 $mount() 方法中生成的,生成后的代碼如下:
(function() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_v("\n " + _s(someVar) + "\n ")])
}
})
可以看到,代碼中會使用到 vm.someVar 屬性,而該屬性最終會代理到之前定義的響應式屬性上,從而調用其 get() 方法,進而調用 dep.depend() 方法將 watcher 添加到訂閱者列表。
dep.depend() => Dep.target.addDep(dep) => dep.addSub(watcher) => dep.subs.push(watcher)
dep.subs 就是 dep 的訂閱者列表,通過這個流程,就建立起了 dep 和 watcher 之間的訂閱關系。
其中,Dep.target 就是當前的 watcher,因為在 watcher.get() 方法執行時,有如下流程:
watcher.get() => pushTarget(watcher) => Dep.target = watcher
第三階段
3秒之后,vm.someVar 被賦予了新的值,從而最終會調用到響應式屬性的 set() 方法,進而調用 dep.notify(),觸發 watcher 更新視圖。
主要流程
dep.notify() => watcher.update() => watcher.run() => watcher.get() => watcher.getter.call(vm, vm) == updateComponent() => vm._update(vm._render())
說明
watcher 的 getter() 方法就是第二階段 new Watcher(vm, updateComponent) 中的 updateComponent 方法,可以看到,通過這個流程,視圖得到了更新。
以上就構成了一個響應式視圖模型,其核心是利用 defineProperty() 方法將普通屬性轉換為帶有鉤子的 set\get 屬性,從而實現了數據監聽。
文章列表