在前面两篇响应原理解析中,主要是实现了Vue
中的data
与methods
与视图的交互,而这篇主要是要唠一唠watch
与computed
的具体实现,完善重要的Watcher
类。
Watch
还是用那个老例子,在new Vue
时,我们加了一个watch
监听message
的变化,在监听到变化的时候在控制台打印出旧值与新值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' }, watch: { message: function (newValue, oldValue) { console.log(oldValue, '-->' , newValue) } }, methods: { changeMsg () { this.message = 'Hello World!' } } })
|
initWatch
我们在实例化Vue
的initState
过程中加上一个initWatch
的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function initWatch (vm, watch) { 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]) } } else { createWatcher(vm, key, handler) } } }
|
initWatch
的实际作用就是批量调用createWatcher
来处理需要监听的key
。而createWatcher
内部实际调用的是Vue
提供的实例方法$watch
。
1 2 3 4 5 6 7 8 9 10 11 12
| function createWatcher (vm, expOrFn, handler, options) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
|
实现$watch
这里我们回顾一下文档上$watch
的用法:
vm.$watch( expOrFn, callback, [options] )
- 参数:
{string | Function} expOrFn
{Function | Object} callback
{Object} [options]
{boolean} deep
{boolean} immediate
- 返回值:
{Function} unwatch
- 用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| vm.$watch('a.b.c', function (newVal, oldVal) { })
vm.$watch( function () { return this.a + this.b }, function (newVal, oldVal) { } )
var unwatch = vm.$watch('a', cb)
unwatch()
|
为了发现对象内部值的变化,可以在选项参数中指定 deep: true
。注意监听数组的变更不需要这么做。如果数组元素中含有对象,则可以监听到数组内部对象的变化。
1 2 3 4 5
| vm.$watch('someObject', callback, { deep: true }) vm.someObject.nestedValue = 123
|
在选项参数中指定 immediate: true
将立即以表达式的当前值触发回调:
1 2 3 4
| vm.$watch('a', callback, { immediate: true })
|
而$watch
的实现,实际上是对Watcher
对象的一种封装,它主要是处理的是immediate
,实际上immediate
就是在创建Watcher
对象后立即执行回调函数。在使用Vue
的时候,有时候我们会将在created
执行的一些处理用immediate
替代,但是需要注意的是,immediate
的执行是早于created
的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Vue.prototype.$watch = function (expOrFn, cb, options) { const vm = this if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {} const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { cb.call(vm, watcher.value) } return function unwatchFn () { watcher.teardown() } }
|
完善Watcher类
由于多了一个deep
的配置项,我们在Watcher
中需要新增一个options
的入参,以及多了一个解除监听的teardown
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| export default class Watcher { constructor ( vm, expOrFn, cb, options ) { vm._watchers.push(this) if (options) { this.deep = !!options.deep } else { this.deep = false } this.vm = vm this.cb = cb this.id = ++uid this.deps = [] this.depIds = new Set() this.expression = expOrFn.toString() if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } this.value = this.get() }
get () { pushTarget(this) const vm = this.vm let value = this.getter.call(vm, vm) if (this.deep) { traverse(value) } popTarget() return value }
addDep (dep) { const id = dep.id if (!this.depIds.has(id)) { this.depIds.add(dep.id) this.deps.push(dep) dep.addSub(this) } }
update () { const value = this.get() if (value !== this.value || isObject(value) || this.deep) { const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } }
teardown () { let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } } }
|
在上面的代码中,如果传入了deep
配置,则需要在执行popTarge()
之前调用traverse
来处理deep
的逻辑,这样才能保证子集收集的依赖是当前这个Watcher
。
traverse
实际上做的就是递归遍历value
的所有子属性,来触发它的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const seenObjects = new Set()
export function traverse (val) { _traverse(val, seenObjects) seenObjects.clear() }
function _traverse (val, seen) { let i, keys const isA = Array.isArray(val) if ((!isA && !isObject(val)) || Object.isFrozen(val)) { return } if (val.__ob__) { const depId = val.__ob__.dep.id if (seen.has(depId)) { return } seen.add(depId) } if (isA) { i = val.length while (i--) _traverse(val[i], seen) } else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[keys[i]], seen) } }
|
Computed
computed
有两种设值方式,下面我们用这个例子来解析一下过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var app = new Vue({ el: '#app', data: { name: 'World', }, computed: { message () { return `Hello,${this.name}` }, message2: { get: function () { return `Goodbye, ${this.name}` }, set: function (value) { this.name = Math.random() } } }, methods: { changeMsg () { this.message2 = '1' } } })
|
initComputed
我们知道计算属性的结果会被缓存,且只有在计算属性所依赖的响应式属性或者说计算计算属性的返回值发生变化时才会重新计算。我们配置了一个lazy
参数,当lazy
值被设置为true
的Watcher
对象,不会立即求值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const computedWatcherOptions = { lazy: true }
function initComputed (vm, computed) { const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) if (!(key in vm)) { defineComputed(vm, key, userDef) } } }
function defineComputed (target, key, userDef) { if (typeof userDef === 'function') { sharedPropertyDefinition.get = createComputedGetter(key) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? createComputedGetter(key) : noop sharedPropertyDefinition.set = userDef.set || noop } Object.defineProperty(target, key, sharedPropertyDefinition) }
|
对计算属性进行数据劫持
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
|
可以发现computed
上的属性的getter
和我们原来在defineReactive
定义的getter
是有不同的。它通过watcher.dirty
属性来判断是否要对值进行重新计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| export default class Watcher { constructor ( vm, expOrFn, cb, options ) { vm._watchers.push(this) if (options) { this.deep = !!options.deep this.lazy = !!options.lazy } else { this.deep = this.lazy = false } this.vm = vm this.cb = cb this.id = ++uid this.dirty = this.lazy this.deps = [] this.depIds = new Set() this.expression = expOrFn.toString() if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } this.value = this.lazy ? undefined : this.get() }
get () { pushTarget(this) const vm = this.vm let value = this.getter.call(vm, vm) if (this.deep) { traverse(value) } popTarget() return value } evaluate () { this.value = this.get() this.dirty = false }
depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } }
addDep (dep) { const id = dep.id if (!this.depIds.has(id)) { this.depIds.add(dep.id) this.deps.push(dep) dep.addSub(this) } }
update () { if (this.lazy) { this.dirty = true } else { this.run() } }
run () { const value = this.get() if (value !== this.value || isObject(value) || this.deep) { const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } }
teardown () { let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } } }
|