首先来一个问题
数据响应式原理真的是双向数据绑定吗?
-
数据响应式原理:
通过数据的改变去驱动 DOM 视图的变化。
-
双向数据绑定:
双向绑定除了数据驱动 DOM 之外, DOM 的变化反过来影响数据,是一个双向的关系。
总结:
所以说,把 vue 的数据响应原理理解为双向数据绑定,实际上这是不准确的。
双向数据绑定
在 Vue 中体现出双向数据绑定作用的方式有两种:
1)v-model 属性
针对于 input 的 v-model 双向数据绑定实际上就是通过子组件中的 $emit 方法派发 input 事件,父组件监听 input 事件中传递的 value 值,并存储在父组件 data 中;然后父组件再通过 prop 的形式传递给子组件 value 值,再子组件中绑定 input 的 value 属性即可。
其他元素使用 v-model 双向数据绑定实际上就是,通过监听 change 事件。以及$emit 方法派发,再通过 prop 的形式传递。
2).sync 修饰符
父组件向子组件传递数据的方式有多种,props 是其中的一种,但是它的局限在于数据只能单向传递,子组件不能直接修改 prop 属性,但是碰到子组件需要修改父组件的情况怎么办呢?
父组件中并没有定义过 update 事件,但是却可以完成 prop 属性 page 的修改,这就是 sync 语法糖的作用。
响应式原理
Vue 采用的是数据劫持结合发布和-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
initState 函数是很多选项初始化的汇总,在 initState 函数内部使用 initProps 函数初始化props 属性;使用 initMethods 函数初始化 methods 属性;使用 initData 函数初始化 data选项;使用 initComputed 函数和 initWatch 函数初始化 computed 和 watch 选项。 initData 为切入点来了解一下 Vue 的响应系统。
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 */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
下面代码对 data 做个判断,如果存在则调用 initData(vm) 函数初始化 data 选项,否则通过 observe 函数观测一个空的对象。其中 observe 函数是将 data 转换成响应式数据的核心入口。
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
下面我们对 initData 函数解析
function initData (vm: Component) {
// data 的赋值操作
let data = vm.$options.data
// typeof 判断的原始因为beforeCreate 生命周期钩子函数是在
// 选项合并阶段之后 initData 之前被调用的,
// 如果在 beforeCreate 生命周期钩子函数中修改了 vm.$options.data 的值,
// 那么在 initData 函数中对于 vm.$options.data 类型的判断就是有存在的必要了。
// vm.$options.data 的类型为函数,则调用 getData 函数获取真正的数据
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// isPlainObject 函数判断变量 data 是不是一个纯对象,
// 如果不是纯对象那么在非生产环境会打印警告信息
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
// 判断data 数据的 key 与 methods 对象中定义的函数名称相同时,发出警告
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 判断data 数据的 key 与 props 中定义的函数名称相同时,发出警告
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
// 判断定义在 data 中的 key 是否是保留键
// isReserved 函数通过判断一个字符串的
// 第一个字符是不是 $ 或 _ 来决定其是否是保留的
// 这么做是为了避免与 Vue 自身的属性和方法相冲突
// Vue 是不会代理那些键名以 $ 或 _ 开头的字段的
// 因为Vue自身的属性和方法都是以 $ 或 _ 开头的
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
对于以上代码主要对 getData 和 proxy 函数做解析
export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
上述代码解析
getData 函数的作用其实就是通过调用 data 函数获取真正的数据对象并返回,即:data.call(vm, vm),如果有错误发生那么则返回一个空对象作为数据对象。
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
上述代码解析
proxy 函数的原理是通过 Object.defineProperty 函数在实例对象 vm 上定义与 data 数据字段同名的访问器属性,并且这些属性代理的值是 vm._data
上对应属性的值。
举例说明:
const vm = new Vue ({
data: {
count: 1
}
})
因为有 proxy 函数的原因,当我们访问 vm.count
时,实际上访问的是vm._data.count
。就是起到了一个数据代理的意思。
下面进入我们的核心正式进入响应式之路。
observe(data, true /* asRootData */)
observe 函数观测数据
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 数据不是一个对象或者是 VNode 实例,则直接 return
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 判断value 是否有"__ob__" 属性
// 如果有是否为 Observer 实例,两者满足把 value.__ob__ 值赋值给ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() && // 用来判断是否是服务端渲染
// 被观测的数据对象必须是数组或者纯对象
(Array.isArray(value) || isPlainObject(value)) &&
// 观测的数据对象必须是可扩展的
Object.isExtensible(value) &&
// Vue实例才拥有_isVue 属性,在此是避免观测Vue实例对象
!value._isVue
) {
// 创建一个Observer实例
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
observe 函数接收两个参数,第一个参数是要观测的数据,第二个参数是一个布尔值,代表将要被观测的数据是否是根级数据。
真正将数据对象转换成响应式数据对象的是 Observer 函数
Observer 构造函数:
首先实例化 Dep 对象,接着通过执行 def 函数把自身实例添加到数据对象 value 的 __ob__
属性上。然后是对数据的分类处理,最终分别调用 defineReactive 函数,定义响应式对象。
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 使用 def 函数,为数据对象定义了一个 __ob__ 属性,
// 这个属性的值就是当前 Observer 实例对象。
def(value, '__ob__', this)
// 判断是否为纯数组,不是数组执行walk函数
if (Array.isArray(value)) {
// hasProto 是一个布尔值,它用来检测当前环境是否可以使用__proto__属性
// 浏览器支持隐式的原型__proro__,则调用protoAugment方法
// arrayMethods 对数组方法的重写
if (hasProto) {
// 直接将数组的实例通过__proto__与arrayMethods对象连接起来。
// 从而继承了arrayMethods上的方法
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
arrayMethods 对数组方法的重写。
由于 Object.defineProperty 对数组的劫持与对象一样,会把数组的索引当作key来监听数组,但是只能监听初始的索引变化。如果使用push或shift来增加索引,默认情况是监听不到的,所以需要再手动初始化才能被observer 。
// 缓存数组原型
const arrayProto = Array.prototype
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto)
// 需要进行功能拓展的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
// 缓存原生数组方法
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
// 执行并缓存原生数组功能
const result = original.apply(this, args)
// 响应式处理
const ob = this.__ob__
let inserted
switch (method) {
// push、unshift会新增索引,所以要手动observer
case 'push':
case 'unshift':
inserted = args
break
// splice方法,如果传入了第三个参数,也会有索引加入,也要手动observer。
case 'splice':
inserted = args.slice(2)
break
}
// 获取插入的值,并设置响应式监听
if (inserted) ob.observeArray(inserted)
// notify change
// 通知依赖更新
ob.dep.notify()
// 返回原生数组方法的执行结果
return result
})
})
关于 def 函数
def 函数是一个非常简单的Object.defineProperty 的封装,这就是为什么我在开发中输出 data 上对象类型的数据,会发现该对象多了一个 __ob__
的属性。
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
好了,说了这么多,重点要来了
defineReactive 函数
defineReactive 的功能就是定义一个响应式对象,给对象动态添加 getter 和 setter。并且实现对数据的依赖收集以及派发更新
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// 获取对象的对象描述
const property = Object.getOwnPropertyDescriptor(obj, key)
// 是否可配置
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 获取原来的get、set
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 递归:继续监听该属性值(只有val为对象时才有childOb)
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 计算value
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 添加依赖
dep.depend()
// 如果有子观察者,也给它添加依赖
if (childOb) {
childOb.dep.depend()
// 如果该属性是数组,查看每项是否含观察者对象,有则添加依赖
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 计算value
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 新旧值是否相等
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
// 不相等,设置新值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 劫持新值到 observe 函数中处理
childOb = !shallow && observe(newVal)
// 发送变更通知
dep.notify()
}
})
}
// dependArray 函数查看每项是否含观察者对象,有则添加依赖
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
Object.defineProperty 方法内的 getter 实现了依赖的收集。 setter 实现了派发更新,而依赖的收集与派发更新都与 Dep 和 Watcher 息息相关,下面我们对这两个函数源码进行分析。
订阅中心 Dep
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
//每个Dep都有唯一的ID
this.id = uid++
// 订阅的信息 用于存放依赖
this.subs = []
}
// 向 subs 数组添加依赖
addSub (sub: Watcher) {
this.subs.push(sub)
}
//移除依赖
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 可以理解为收集依赖的事件,不考虑其他方面的话 功能等同于addSub()
// 此方法的作用等同于 this.subs.push(Watcher);
// 也及是调用watcher的adddep方法实现watcher和dep相互引用
// 设置某个Watcher的依赖
// 这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用
// 也就是说判断他是Watcher的this.get调用的,而不是普通调用
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 这个方法更为直观了,执行所有依赖的update()方法,改变视图。
// 通知所有绑定 Watcher。调用watcher的update()
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep.target = null;
订阅者 Watcher
在initMixin()初始化完成Vue实例所有的配置之后,在最后根据el是否存在,调用$mount()实现挂载。
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
在 $mount 中最后 执行的是return mountComponent(this, el, hydrating)
在 mountComponent() 方法中记住以下两段代码
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
mountComponent 在构造新的Watcher对象传了当前vue实例、updateComponent函数、空函数、options 还有个布尔值。
下面看一下watcher 的源码:
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
// 当前Watcher添加到vue实例上
vm._watchers.push(this)
// 参数配置,options默认false
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
// this.lazy 如果设置为 true 则在第一次 get 的时候才计算值
// 初始化的时候并不计算。默认值为 true
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
// 用于计算属性
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
//内容不可重复的数组对象
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
//将watcher对象的getter设为updateComponent方法
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
get 函数会把我们 watcher 推送到,dep.target 中
Watcher 的 get 方法实际上就是调用了 updateComponent 方法,调用这个函数会接着调用 _update
函数更新 dom ,这个是挂载到 vue 原型的方法,而_render
方法重新渲染了 vnode 。
get () {
// 将Dep的target添加到targetStack,同时Dep的target赋值为当前watcher对象
pushTarget(this)
let value
const vm = this.vm
try {
// 调用updateComponent方法。getter 属于取值操作。
// 就会调用defineReactive 中的 getter 函数。
// 所以此时会执行 dep.depend() 函数 调用 watcher.addDep(dep)
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)
}
// update执行完成后,又将Dep.target从targetStack弹出。
// pushTarget(this) 方法与 popTarget()方法主要做的就是入栈出栈的一个操作
popTarget()
this.cleanupDeps()
}
return value
}
addDep 函数
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)
}
}
}
当执行 addDep 的时候会把 dep 存起来,这个函数明显是用来去重的。
当调用 this.cleanupDeps ,这个函数会把 newDepIds 的值赋给 depIds,然后把 newDepIds 清空。
update 函数
当执行到 dep.notify 时,也就是调用 watcher.update() 方法。watcher.update 函数默认是异步更新的。所以默认会走 queueWatcher 方法
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
queueWatcher 函数
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
/*
* 注意这里是==而不是===
* 如果has[id]不存在,则has[id]为undefined,undefined==null结果为true
* 如果has[id]存在且为null,则为true
* 如果has[id]存在且为true,则为false
* 这个if表示,如果这个watcher尚未被flush则return
*/
if (has[id] == null) {
has[id] = true
if (!flushing) {
/* 如果当前不是正在更新watcher数组的话,那watcher会被直接添加到队列末尾 */
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
/* 这个循环其实是在处理边界情况。
* 即:在watcher队列更新过程中,用户再次更新了队列中的某个watcher
*/
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
// 调用 flushSchedulerQueue() 刷新队列并执行 watcher。
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
flushSchedulerQueue 函数
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// 以watcher.id对watcher进行有小到大排序 */
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
/* 这里没有将queue.length缓存起来是因为在执行期间还会添加进新的watcher */
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
/* 一般是执行beforeUpdate钩子函数 */
watcher.before()
}
id = watcher.id
has[id] = null
/* run方法会执行watcher的callback方法 */
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
上述函数解析:
这个函数其实很好理解,就是将更新队列中的watcher拿出来并依次调用他们的callback,但重点在于为什么在for循环之前先对这些watcher进行了升序排列呢? 这处于2点考虑。
-
确保watcher的更新顺序与它们被创建的顺序一致。
-
对于父子组件来说,组件的创建顺序是父组件先被创建,然后子组件再被创建,而renderWatcher是在组件实例创建过程中被一同创建的,因此父组件的renderWatcher的id是小于子组件的
-
对于用户自定义watcher和renderWatcher来说,某个组件的用户自定义watcher是先于组件的renderWatcher被创建的,因此用户自定义watcher的id小于renderWatcher的id。
-
-
一旦在更新循环的执行过程中父组件所引用的某个子组件被销毁,则会跳过子组件的watcher。
对于 Watcher 的详细文字描述:
描述来源:blog.csdn.net/weixin_4059…
Watcher 分为三种:
- Computed Watcher;
- 用户 Watcher (监听器);
- 渲染 Watcher
渲染 Watcher 的创建时机:src/core/instance/lifecycle.js。
渲染 watcher 创建的位置: lifecycle.js 的 mountComponent 函数中。
Watcher 是没有静态方法的,因为 $watch 方法中要使用 Vue 的实例。创建顺序:计算属性 Watcher、用户 Watcher (监听器)、渲染 Watcher
Watcher 创建的工作流程:
首先 Watcher 的构造函数初始化,处理 expOrFn (渲染 watcher 和监听器处理不同)。 =>调用 this.get(),它里面调用 pushTarget() 然后 this.getter.call(vm, vm) (对于渲染 watcher 调用 updateComponent),如果是用户 watcher 会获取属性的值 (触发 get 操作)。
当数据更新的时候,dep 中调用 notify() 方法。 =>notify() 中调用 watcher 的 update() 方法。 => 在update() 中调用 queueWatcher()。 => queueWatcher() 是一个核心方法,去除重复操作,调用 flushSchedulerQueue() 刷新队列并执行 watcher。
并且在flushSchedulerQueue() 中对 watcher 排序,遍历所有 watcher,如果有 before,触发生命周期的钩子函数 beforeUpdate,执行 watcher.run(),它内部调用 this.get(),然后调用 this.cb() (渲染 watcher 的 cb 是 noop) 整个流程结束。
查看渲染 watcher 的执行过程
当有数据发生更新时:
- 首先defineReactive 的 set 方法中调用 dep.notify()。
- 然后调用 watcher 的 update()
- 再调用 queueWatcher(),把 watch 存入队列,如果已经存入,不重复添加
- 接着循环调用 flushSchedulerQueue(),并通过 nextTick(),在消息循环结束之前时调用 flushSchedulerQueue()。
- 最后调用 watcher.run():通过调用 watcher.get() 获取最新值。如果是渲染 watcher 结束,如果是用户 watcher,调用 this.cb()。
总结:
对于响应式原理中重要的三个函数, Watcher, Observer , Dep 的关系全都梳理完成。而这些也是 Vue 实现的核心逻辑之一。再来简单总结一下三者的关系,其实是一个简单的 观察-订阅 的设计模式, 简单来说就是, 观察者观察数据状态变化, 一旦数据发生变化,则会通知对应的订阅者,让订阅者执行对应的业务逻辑 。我们熟知的事件机制,就是一种典型的观察-订阅的模式
- Observer, 观察者,用来观察数据源变化.
- Dep, 观察者和订阅者是典型的 一对多 的关系,所以这里设计了一个依赖中心,来管理某个观察者和所有这个观察者对应的订阅者的关系, 消息调度和依赖管理都靠它。
- Watcher, 订阅者,当某个观察者观察到数据发生变化的时候,这个变化经过消息调度中心,最终会传递到所有该观察者对应的订阅者身上,然后这些订阅者分别执行自身的业务回调即可 参考