Vue响应系统
vue会遍历 data 中的对象,通过使用 Object.defineProperty 将这些属性全部转为 getter/setter,在属性被访问和修改时通知变化,每个组件都有相应的 watcher 实例对象,他会在组件渲染过程中把属性记录,当属性的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件更新。
vue响应系统,核心三点:observe,watcher,dep:
observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持
dep:每个属性拥有自己的消息订阅dep,用于存放所有订阅了该属性的观察者对象
watcher:观察者通过dep实现随响应属性的监听,监听到结果后,主动触发自己的回调进行响应
initState函数
// 计算属性的初始化是在 initState 函数中完成的,这个方法就是初始化 props,data,methods,watch,computed 等属性。
export 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 */)
}
// computed初始化
// 调用了initComputed函数,传入了两个参数vm实例和opt.computed开发者定义的computed选项
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
初始化Computed
// 初始化计算属性
function initComputed (vm: Component, computed: Object) {
...
// 遍历 computed 计算属性
for (const key in computed) {
...
// 创建 Watcher 实例
// 四个参数:vm 实例、getter求值函数、noop 空函数、computedWatcherOptions 常量对象(在这里提供给 Watcher 一个标识 {computed:true} 项,表明这是一个计算属性而不是非计算属性的观察者,watchers为vm._computedWatchers对象的引用
watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
// 创建属性 vm.reversedMessage,并将提供的函数将用作属性 vm.reversedMessage 的 getter,
// 最终 computed 与 data 会一起混合到 vm 下,所以当 computed 与 data 存在重名属性时会抛出警告
defineComputed(vm, key, userDef)
...
}
}
export function defineComputed (target: any, key: string, userDef: Object | Function) {
...
// 创建 get set 方法
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
...
// 创建属性 vm.reversedMessage,并初始化 getter setter
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
// 通过计算属性key拿到对应的watcher,并启动观察者进行观察
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// dirty标志数据是否发生变化
if (watcher.dirty) {
// 执行watcher.get()方法
watcher.evaluate()
}
// 判断是否处于依赖收集状态
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
watcher
/**
* watchers[key] = new Watcher(
* vm, vm实例
* getter || noop, getter求值函数
* noop, noop空函数
* computedWatcherOptions 配置对象:{computed:true},表明这是一个计算属性的观察者
) */
class Watcher {
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
if (options) {
this.computed = !!options.computed
}
//根据{computed:true} 创建Dep实例,该属性的消息订阅器
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
get() {
// this指的是watcher实例,将当前watcher实例暂存到Dep.target,表示开启依赖收集任务
pushTarget(this)
let value
const vm = this.vm
try {
// 执行过程中可以手机到vm.reversedMesage的依赖
value = this.getter.call(vm, vm)
} catch (e) {
} finally {
// 结束依赖收集任务
popTarget()
}
return value
}
update() {
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true
} else {
this.getAndInvoke(() => {
this.dep.notify()
})
}
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
evaluate() {
// dirty标志数据是否发生变化
if (this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value
}
depend() {
if (this.dep && Dep.target) {
this.dep.depend()
}
}
}
Dep
// dep对象中两个属性,存储id
class Dep {
static target: ?Watcher;
subs: Array<Watcher>;
constructor() {
//标识当前Dep id
this.id = uid++
// 存放所有的监听watcher
this.subs = []
}
// 添加一个观察者对象
addSub(sub: Watcher) {
this.subs.push(sub)
}
// Dep.target作用:只有需要的时候才会收集依赖
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 调用watcher更新
notify() {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
// update是给依赖变化时使用的,包含对watch的处理
subs[i].update()
}
}
}
Dep.target = null
const targetStack = []
export function pushTarget(_target: Watcher) {
// 在一次依赖收集期间,如果有其他依赖收集任务开始(比如:当前 computed 计算属性嵌套其他 computed 计算属性),
// 那么将会把当前 target 暂存到 targetStack,先进行其他 target 的依赖收集,
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget() {
// 当嵌套的依赖收集任务完成后,将 target 恢复为上一层的 Watcher,并继续做依赖收集
Dep.target = targetStack.pop()
}
watcher中实例化dep并向dep.subs中添加了订阅者,dep通过notify方法遍历dep.subs通知每个watcher更新
总结
-
当组件初始化的时候,computed和data会分别创建各自的响应系统,Observer遍历data中每个属性设置get/set数据拦截
-
初始化computed调用initComputed函数
- 注册一个watcher实例,并实例化一个Dep消息订阅器用作后续猴急依赖(比如渲染函数的watcher或者其他观察该计算属性变化的watcher)
- 调用计算属性会触发Object.defineProperty的get访问器函数
- 调用watcher.depend()方法向自身的消息订阅dep的subs中添加其他属性的watcher
- 调用watcher的evaluate方法(进而调用watcher的get方法)让自身成为其他watcher的消息订阅器的订阅者,首先将watcher赋给Dep.target,然后执行getter求值函数,当访问求值函数里面的属性,同样触发他们的get访问器函数从而将该计算属性的watcher添加到求值函数中属性的watcher的消息订阅器dep中,当这些操作完成后,最后关闭Dep.target赋给null并返回求值函数结果
-
当某个属性发生变化,触发set拦截函数,然后调用自身消息订阅器dep的notify方法,遍历当前dep中保存着所有订阅者watcher的subs数组,并逐个调用watcher的update方法,完成响应更新。