【Vue】watch中的deep:true源码实现
当用户指定了watch中的deep属性为true时,如果当时监控的属性是数组类型,会对对象中的每一项进行求值,此时会将当前watcher存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数据更新。内部原理就是递归,耗费性能 。
整体流程:
initWatch 初期化user watcher(1),user watcher在defineReactive的get中订阅属性的变化(2),在defineReactive的set时触发notify(2),notify调用每个订阅了改属性变化的watcher的update(3),监听 watcher 进入update的queueWatcher,在queueWatcher的nextTick中调用flushSchedulerQueue(4),flushSchedulerQueue中调用监听watcher的run(5),在watcher.run中调用this.get,在this.get中判断this.deep是否为true,为true则执行traverse,在traverse中会因为对属性的取值触发2的get方法,并且traverse递归调用,使当前watch监听到对象内部的每一个属性(6),进而调用用户在watch属性上定义的方法。
1、initWatch 初期化user watcher(src\core\instance\state.js)
function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i])//每一项创建一个watcher } } else { createWatcher(vm, key, handler) } } }
2、user watcher在defineReactive的get中订阅属性的变化,在defineReactive的set时触发notify
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { ... 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) { ... dep.notify() } }) }
3、notify调用每个订阅了改属性变化的watcher的update
notify () { ... for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
4、queueWatcher(src\core\observer\scheduler.js):
export function queueWatcher (watcher: Watcher) { ... nextTick(flushSchedulerQueue) }
5、调用监听watcher的run
function flushSchedulerQueue () { ... for (index = 0; index < queue.length; index++) { ... watcher.run() ... } ... }
6、在this.get中判断this.deep是否为true,为true则执行traverse
get() { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e: any) { 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 }
7、traverse