上一章,咱们讲到了Vue
初始化做的一些操作,那么咱们这一章来讲一个Vue
外围概念响应式零碎
。
咱们先来看一下官网对深刻响应式零碎
的解释:
当你把一个一般的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性。并应用 Object.defineProperty 把这些属性全副转为 getter/setter。Object.defineProperty 是 ES5 中一个无奈 shim 的个性。这也就是为什么 Vue 不反对 IE8 以及更低版本浏览器的起因。
上图是Vue官网放出的一张图,而且提到外围概念Object.defineProperty
,那么咱们间接看源码,咱们看到的Object.defineProperty
在defineReactive
函数的外部,而defineReactive
函数在walk
函数外部,顺次找到源头是Observer
类
./core/observer/indexexport class Observer { value: any; dep: Dep; vmCount: number; // number of vms that has this object as root $data /** * 生成的Observer实例上挂载三个属性 * 1. value, 即观测数据对象自身 * 2. dep, 用于依赖收集的容器 * 3. vmCount, 间接写死为0 */ constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 // 在观测数据对象上增加__ob__属性, 是Observer实例的援用 // def相当于Object.defineProperty, 区别是dep里会把__ob__属性设置为不可枚举 // 须要留神的是, value.__ob__.value 显然就是 value 自身, 这里有一个循环援用 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } // 用于解决对象类型的观测值, 循环所有的key都调用一次defineReactive walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } // 对数组的每一项进行监听 observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }}
value
是须要被察看的数据对象,在构造函数中,会给value
减少ob
属性,作为数据曾经被Observer
察看的标记。如果value
是数组
,就应用observeArray
遍历value
,对value
中每一个元素调用observe
别离进行察看。如果value
是对象
,则应用walk
遍历value
上每个key
,对每个key
调用defineReactive
来取得该key
的set/get
控制权。
那么说到如果value
是数组的话,调用observeArray
办法遍历数组,开端还调用了observe
函数,那到底这个函数有什么用呢?咱们来一探到底:
// 用于观测一个数据export function observe (value: any, asRootData: ?boolean): Observer | void { // 对于不是object或者是vnode实例的数据, 间接返回, 不会进行观测 if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void // 如果数据上已有__ob__属性, 阐明该数据曾经被观测, 不再反复解决 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ // 要观测一个数据须要满足以下条件: // 1. shouldObserve为true, 这是一个标记位, 默认为true, 某些非凡状况下会改成false // 2. !isServerRendering(), 不能是服务端渲染 // 3. Array.isArray(value) || isPlainObject(value), 要观测的数据必须是数组或者对象 // 4. Object.isExtensible(value). 要观测的数据必须是可扩大的 // 5. !value._isVue, 所有vue实例的_isVue属性都为true, 防止观测vue实例对象 } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob}
能够见得observe
函数的作用是:查看对象上是否有ob
属性,如果存在,则表明该对象曾经处于Observer
的察看中,如果不存在,则new Observer
来察看对象。
回到上文,数组说完了,那么来说对象的函数walk调用,咱们看到间接是调用了defineReactive函数,那咱们来一探到底:
// 定义响应式对象, 给对象动静增加get set拦挡办法,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 const getter = property && property.get if (!getter && arguments.length === 2) { val = obj[key] } const setter = property && property.set let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { 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) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ // 判断NaN的状况 if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } })}
能够见得defineReactive
函数的作用是:通过Object.defineProperty
设置对象的key
属性,使得可能捕捉到该属性值的set/get
操作,且observe函数深度遍历,所以把所有的属性都增加到了Observe
下面了,也就是说,咱们对数据的读写就会触发getter/setter
,再者咱们能够看到get
办法外面有Dep.target
这个变量,dep.depend
,dependArray
,set
办法外面有dep.notify
这些办法,可想而知,咱们依赖了Dep
这个文件:
export default class Dep { // target是一个全局惟一的Watcher static target: ?Watcher; id: number; subs: Array<Watcher>; // 生成每个实例惟一的uid, subs用于存储watcher constructor () { this.id = uid++ this.subs = [] } // 增加一个watcher addSub (sub: Watcher) { this.subs.push(sub) } // 删除一个watcher removeSub (sub: Watcher) { remove(this.subs, sub) } // 将本身退出到全局的watcher中 depend () { if (Dep.target) { Dep.target.addDep(this) } } // 告诉所有订阅者 notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }}
察看dep文件,咱们能够看到一个Dep
类,其中有几个办法:
addSub:
接管的参数为Watcher
实例,并把Watcher
实例存入记录依赖的数组中removeSub:
与addSub
对应,作用是将Watcher
实例从记录依赖的数组中移除depend:
Dep.target
上寄存这以后须要操作的Watcher
实例,调用depend
会调用该Watcher
实例的addDep
办法。notify:
告诉依赖数组中所有的watcher
进行更新操作
而且发明了一个subs
用来存储订阅者。
剖析完了之后,咱们就总结出一句话,dep
是一个用来存储所有订阅者watcher
的对象,他的notify
办法就是去遍历告诉所有的Watcher
订阅者数据源产生了扭转须要去更新视图了。
那么咱们再来看一下Watcher的构造是咋样的:- 参考Vue3源码视频解说:进入学习
export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync } else { 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 if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} 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() } /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) let value const vm = this.vm try { 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) } popTarget() this.cleanupDeps() } return value } /** * 接管参数`dep(Dep实例)`,让以后`watcher`订阅`dep` */ 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) } } } /** * 革除`newDepIds和newDep`上记录的对dep的订阅信息 */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * 立即运行`watcher`或者将`watcher`退出队列中期待对立`flush` */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * 运行`watcher`,调用`this.get()`求值,而后触发回调 */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } } /** *调用`this.get()`求值 */ evaluate () { this.value = this.get() this.dirty = false } /** * 遍历`this.deps`,让以后`watcher`实例订阅所有`dep` */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** *去除以后`watcher`实例所有的订阅 */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } }}
咱们看到了一个Watcher类,并且有一些办法:
get:
将Dep.target
设置为以后watcher
实例,在外部调用this.getter
,如果此时某个被Observer
察看的数据对象被取值了,那么以后watcher
实例将会主动订阅数据对象的Dep
实例addDep:
接管参数dep(Dep实例)
,让以后watcher
订阅dep
cleanupDeps:
革除newDepIds和newDep
上记录的对dep的订阅信息update:
立即运行watcher
或者将watcher
退出队列中期待对立fresh
run:
运行watcher
,调用this.get()
求值,而后触发回调evaluate:
调用this.get()
求值depend:
遍历this.deps
,让以后watcher
实例订阅所有dep
teardown:
去除以后watcher
实例所有的订阅
那么咱们晓得这么多办法,来梳理一下流程
咱们的数据发生变化,咱们data
外面所有的属性都能够看做一个dep
,而dep
外面的subs
就是寄存以后属性的中央,当咱们数据发生变化的时候就不会被监听到,咱们就要通过dep
去调用notify
办法告诉所有的Watcher
进行更新视图。
那么问题又来了,这个this.subs
是如何增加订阅者的?
get () { pushTarget(this) let value const vm = this.vm try { 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) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ 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) } } }
咱们在Dep
中能够看到Dep
在一开始定义了一个全局属性Dep.target
,在新建watcher
是,这个属性为null
,而在watcher
的构造函数中最初会执行本人的get()
办法,进而执行pushTarget(this)
办法:
// 将watcher实例赋值给Dep.target,用于依赖收集。同时将该实例存入target栈中export function pushTarget (_target: ?Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target}
能够看到get()
办法,value = this.getter.call(vm, vm)
,而后popTarget()
办法:
// 从target栈取出一个watcher实例export function popTarget () { Dep.target = targetStack.pop()}
Dep.target
只是一个标记,存储以后的watcher
实例,触发Object.defineProperty
中的get
拦挡,而在Oject.defineProperty
中的get
那里,咱们能够看到dep.depend()
,正是在这里将以后的订阅者watcher
绑定当Dep
上。
也就是说,每个watcher
第一次实例化的时候,都会作为订阅者订阅其相应的Dep
。
写到这里,置信各位对数据响应式
曾经有很粗浅的了解了吧,那么咱们还有一个话题,咱们是如何进行初始化渲染更新
和二次更新视图
的?下章咱们讨论一下。