Vue 響應(yīng)式是什么
Vue 是一個 MVVM 的框架,即 Model-View-ViewModel宇色,Model 與 View 之間不直接聯(lián)系,而是由 ViewModel(相當(dāng)于 Pipe) 去監(jiān)聽 Model 的變化并觸發(fā) View 改變以及監(jiān)聽 View 中的事件操作響應(yīng)的 Model
我們來看一段簡單的雙向綁定的例子:
<template>
<div id="app">
<input
type="text"
v-model="msg"
placeholder="edit me"
>
<h1 @click="rotate()">{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
msg: 'Welcome'
}
},
methods: {
rotate () {
this.msg = this.msg.split('').reverse().join('')
}
}
}
</script>
在這個例子中,<input>和<h1>均為 view灵寺,msg 為 model蹦骑,而 Vue 則是我們的 ViewModel慈省。
Vue 提供了兩個工具:
DOMListener:監(jiān)聽頁面的 DOM 事件,修改 Model 中的數(shù)據(jù)
DataBinding:監(jiān)聽 Js 中的數(shù)據(jù)變化眠菇,修改 View 視圖
Vue 如何實現(xiàn)響應(yīng)式
先來了解幾個名詞
Observer 觀察者(監(jiān)聽器)边败,每個可監(jiān)聽對象都會掛載一個觀察者實例,負責(zé)訂閱數(shù)據(jù)變化捎废,通知對應(yīng)的 Dep 實例
Dep 消息訂閱器(依賴收集器)笑窜,負責(zé)依賴收集,管理 watcher缕坎,依賴收集操作在獲取數(shù)據(jù)時執(zhí)行(defineReactive > get)
-
Watcher 訂閱者怖侦,負責(zé)響應(yīng),Vue中有三種
-
User Watcher 組件的 watch 中定義的 watcher
于 initWatch > createWatcher 中初始化
-
Computed Watcher 組件的 computed 中定義的 watcher
于 initComputed 中初始化
-
Render Watcher 渲染 Watcher,只要有數(shù)據(jù)變化匾寝,最終都會由 Render Watcher 觸發(fā)頁面更新
于 mountComponent 方法中 beforeMount 與 mounted 鉤子之間初始化
-
再來看一下響應(yīng)的流程
來自官網(wǎng)的盜圖
稍微加工了一下搬葬,看下圖
實線部分為內(nèi)部實現(xiàn);虛線部分為響應(yīng)的流程艳悔。
結(jié)合上圖急凰,簡單來說就是在 DOM 上操作數(shù)據(jù)(如 input )時,會被 Observer 中定義的 setter 函數(shù)劫持并調(diào)用 notify 函數(shù)通知消息訂閱器 Dep猜年,Dep 遍歷其 subs 數(shù)組對所有的訂閱者 Watcher 調(diào)用 update 函數(shù)抡锈,update 函數(shù)通過一系列操作更新 DOM
這一系列操作包括:
- queueWatcher 將當(dāng)前 Watcher 放入待更新的 Watcher 隊列中
- flushSchedulerQueue 依次執(zhí)行隊列中 Watcher 的 run 函數(shù)
- run 函數(shù)中
const value = this.get()
調(diào)用 Watcher 的 get 函數(shù) - get 函數(shù)中執(zhí)行
value = this.getter.call(vm, vm)
調(diào)用 Watcher 中定義的 expression- Render Watcher 中的 expression 是
function () { vm._update(vm._render(), hydrating); }
- User Watcher 中的 expression 是用戶自定義的 watch 中的函數(shù)
- Computed Watcher 中的 expression 是用戶自定義的 computed 中回調(diào)函數(shù)
- Render Watcher 中的 expression 是
- 執(zhí)行
vm._update(vm._render(), hydrating)
,vm._render() 返回一個新的 VNode,vm._update 中執(zhí)行vm.__patch__(prevVnode, vnode)
將VNode 渲染成真實 DOM 反應(yīng)在頁面上
接下來分別看這三者的實現(xiàn)
-
Observer
/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. */ export class Observer { ... constructor(value: any) { ... def(value, '__ob__', this) ... this.walk(value); } walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } ... }
撇開具體的邏輯代碼不看乔外,Observer 類的構(gòu)造函數(shù)中就做了兩件事
- 將 Observer 的實例掛載到數(shù)據(jù)對象上
def(value, '__ob__', this)
床三, - 循環(huán)執(zhí)行 defineReactive 方法將數(shù)據(jù)對象的所有屬性變成響應(yīng)式的
- 將 Observer 的實例掛載到數(shù)據(jù)對象上
DefineReactive
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
...
const getter = property && property.get
const setter = property && property.set
...
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
...
dep.depend()
...
}
return value
},
set: function reactiveSetter (newVal) {
...
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
...
dep.notify()
}
})
}
defineReactive 做了一下幾件事
- 實例化一個消息訂閱器 new Dep()
- 利用 Object.defineProperty 給 data 對象上的每個屬性添加 getter 和 setter 以"劫持"數(shù)據(jù)操作(get / set)
- 在 get 中調(diào)用 dep 的 depend 方法將當(dāng)前 Watcher 掛載到當(dāng)前 dep 上
- 在 set 時調(diào)用 dep 的 notify 方法通知所有訂閱該 dep 的 Watcher
注意: 這里的 Dep.target 表示當(dāng)前正在計算的 Watcher,其具有全局唯一性杨幼。
-
Dep
/** * A dep is an observable that can have multiple * directives subscribing to it. * directives 中的通過 Watcher 訂閱數(shù)據(jù) */ 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 () { ... for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() // 調(diào)用 Watcher 的 update 方法 } } }
Dep 類作為消息訂閱器撇簿,只負責(zé)依賴收集(通過 depend 與 addSub 收集所有與之相關(guān)的 Watcher )和管理訂閱它的所有 Watcher。
-
Watcher
/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */ export default class Watcher { ... constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm // 當(dāng)前Vue實例 if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // 添加render watcher // options ... // parse expression for getter 下面用到的 getter 函數(shù)就是這么來的 if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop ... } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. * 調(diào)用 this.getter 函數(shù)(user差购、computed四瘫、render watcher 的 callback) * 然后重新收集依賴 */ get () { ... value = this.getter.call(vm, vm) ... this.cleanupDeps() ... return value } /** * Add a dependency to this directive. * 把 dep 添加到 Watcher 實例的依賴數(shù)組中 */ addDep (dep: Dep) { ... dep.addSub(this) } /** * Clean up for dependency collection. * 重新整理依賴數(shù)組(deps) */ cleanupDeps () { ... this.deps = this.newDeps ... } /** * Subscriber interface. * Will be called when a dependency changes. * 同步 sync 直接執(zhí)行 run * 異步 async 則先把當(dāng)前 Watcher 推入隊列,在 nextTick 中通過 flushSchedulerQueue 循環(huán)執(zhí)行每個 watch 的 run 方法 */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { if (this.active) { /** * 對 watcher 求值欲逃,重新收集依賴 * watcher 的執(zhí)行順序是 user > computed > render * get 函數(shù)內(nèi)部做了兩件事: * 觸發(fā) watcher callback找蜜;返回當(dāng)前 value 值(只有 user watcher 有返回值) */ const value = this.get() if ( value !== this.value || isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value ... this.cb.call(this.vm, value, oldValue) ... } } } ... /** * Depend on all deps collected by this watcher. * 循環(huán)收集依賴 */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. * 把當(dāng)前 watcher 從其依賴的所有 dep 的 subs 數(shù)組中刪除 */ teardown () { ... } }
Watcher 中的方法不多,主要就以下幾個
- addDep稳析、depend洗做、cleanupDeps、teardown 主要用于管理 watcher 與 dep 的依賴關(guān)系
- get迈着、update竭望、run 用于響應(yīng)數(shù)據(jù)更新的操作(如更新視圖等)
一些小知識
由于 Vue 響應(yīng)式的核心 defineReactive 是使用 ES5 的Object.defineProperty 實現(xiàn)的,所以不支持 IE8 以下的瀏覽器
__patch__
過程關(guān)于 DOM 操作的部分都定義在 platforms > runtime > node-ops.js 中-
Vue 不能檢測到對象屬性的添加和刪除裕菠,需要通過 Vue.$Set(target,key,value) 去實現(xiàn)咬清,set 函數(shù)中會重新執(zhí)行 defineReactive 將對象變?yōu)轫憫?yīng)式,并且調(diào)用 dep.notify 以達到更新視圖的效果
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ export function set (target: Array<any> | Object, key: any, val: any): any { ... // 如果屬性已經(jīng)存在就直接返回 if (key in target && !(key in Object.prototype)) { target[key] = val return val } // 將新添加的屬性變成響應(yīng)式 // 觸發(fā) notify const ob = (target: any).__ob__ ... defineReactive(ob.value, key, val) ob.dep.notify() return val }