Vue 响应式是什么
Vue 是一个 MVVM 的框架,即 Model-View-ViewModel,Model 与 View 之间不直接联系,而是由 ViewModel(相当于 Pipe) 去监听 Model 的变化并触发 View 改变以及监听 View 中的事件操作响应的 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:监听页面的 DOM 事件,修改 Model 中的数据
DataBinding:监听 Js 中的数据变化,修改 View 视图
Vue 如何实现响应式
先来了解几个名词
Observer 观察者(监听器),每个可监听对象都会挂载一个观察者实例,负责订阅数据变化,通知对应的 Dep 实例
Dep 消息订阅器(依赖收集器),负责依赖收集,管理 watcher,依赖收集操作在获取数据时执行(defineReactive > get)
-
Watcher 订阅者,负责响应,Vue中有三种
-
User Watcher 组件的 watch 中定义的 watcher
于 initWatch > createWatcher 中初始化
-
Computed Watcher 组件的 computed 中定义的 watcher
于 initComputed 中初始化
-
Render Watcher 渲染 Watcher,只要有数据变化,最终都会由 Render Watcher 触发页面更新
于 mountComponent 方法中 beforeMount 与 mounted 钩子之间初始化
-
再来看一下响应的流程
来自官网的盗图
稍微加工了一下,看下图
实线部分为内部实现;虚线部分为响应的流程。
结合上图,简单来说就是在 DOM 上操作数据(如 input )时,会被 Observer 中定义的 setter 函数劫持并调用 notify 函数通知消息订阅器 Dep,Dep 遍历其 subs 数组对所有的订阅者 Watcher 调用 update 函数,update 函数通过一系列操作更新 DOM
这一系列操作包括:
- queueWatcher 将当前 Watcher 放入待更新的 Watcher 队列中
- flushSchedulerQueue 依次执行队列中 Watcher 的 run 函数
- run 函数中
const value = this.get()
调用 Watcher 的 get 函数 - get 函数中执行
value = this.getter.call(vm, vm)
调用 Watcher 中定义的 expression- Render Watcher 中的 expression 是
function () { vm._update(vm._render(), hydrating); }
- User Watcher 中的 expression 是用户自定义的 watch 中的函数
- Computed Watcher 中的 expression 是用户自定义的 computed 中回调函数
- Render Watcher 中的 expression 是
- 执行
vm._update(vm._render(), hydrating)
,vm._render() 返回一个新的 VNode,vm._update 中执行vm.__patch__(prevVnode, vnode)
将VNode 渲染成真实 DOM 反应在页面上
接下来分别看这三者的实现
-
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 类的构造函数中就做了两件事
- 将 Observer 的实例挂载到数据对象上
def(value, '__ob__', this)
, - 循环执行 defineReactive 方法将数据对象的所有属性变成响应式的
- 将 Observer 的实例挂载到数据对象上
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 以"劫持"数据操作(get / set)
- 在 get 中调用 dep 的 depend 方法将当前 Watcher 挂载到当前 dep 上
- 在 set 时调用 dep 的 notify 方法通知所有订阅该 dep 的 Watcher
注意: 这里的 Dep.target 表示当前正在计算的 Watcher,其具有全局唯一性。
-
Dep
/** * A dep is an observable that can have multiple * directives subscribing to it. * directives 中的通过 Watcher 订阅数据 */ 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() // 调用 Watcher 的 update 方法 } } }
Dep 类作为消息订阅器,只负责依赖收集(通过 depend 与 addSub 收集所有与之相关的 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 // 当前Vue实例 if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // 添加render watcher // options ... // parse expression for getter 下面用到的 getter 函数就是这么来的 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. * 调用 this.getter 函数(user、computed、render watcher 的 callback) * 然后重新收集依赖 */ get () { ... value = this.getter.call(vm, vm) ... this.cleanupDeps() ... return value } /** * Add a dependency to this directive. * 把 dep 添加到 Watcher 实例的依赖数组中 */ addDep (dep: Dep) { ... dep.addSub(this) } /** * Clean up for dependency collection. * 重新整理依赖数组(deps) */ cleanupDeps () { ... this.deps = this.newDeps ... } /** * Subscriber interface. * Will be called when a dependency changes. * 同步 sync 直接执行 run * 异步 async 则先把当前 Watcher 推入队列,在 nextTick 中通过 flushSchedulerQueue 循环执行每个 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 的执行顺序是 user > computed > render * get 函数内部做了两件事: * 触发 watcher callback;返回当前 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. * 循环收集依赖 */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. * 把当前 watcher 从其依赖的所有 dep 的 subs 数组中删除 */ teardown () { ... } }
Watcher 中的方法不多,主要就以下几个
- addDep、depend、cleanupDeps、teardown 主要用于管理 watcher 与 dep 的依赖关系
- get、update、run 用于响应数据更新的操作(如更新视图等)
一些小知识
由于 Vue 响应式的核心 defineReactive 是使用 ES5 的Object.defineProperty 实现的,所以不支持 IE8 以下的浏览器
__patch__
过程关于 DOM 操作的部分都定义在 platforms > runtime > node-ops.js 中-
Vue 不能检测到对象属性的添加和删除,需要通过 Vue.$Set(target,key,value) 去实现,set 函数中会重新执行 defineReactive 将对象变为响应式,并且调用 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 { ... // 如果属性已经存在就直接返回 if (key in target && !(key in Object.prototype)) { target[key] = val return val } // 将新添加的属性变成响应式 // 触发 notify const ob = (target: any).__ob__ ... defineReactive(ob.value, key, val) ob.dep.notify() return val }