computed 实现原理
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值
那么计算属性为何可以做到当它的依赖项发生改变时才会进行重新的计算,否则当前数据是被缓存的?
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export default { computed: { status() { return this.$store.state.status }, fullName: { get: function() { return this.firstName + ' ' + this.lastName }, set: function(newValue) { var names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] } } } }
|
实现原理
- 初始化
computed
遍历当前实例下computed的所有属性,源码src/core/instance/state.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const computedWatcherOptions = { lazy: true } function initComputed(vm: Component, computed: Object) { for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) { watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } if (!(key in vm)) { defineComputed(vm, key, userDef) } } }
|
- 将定义的computed属性的每一项使用Watcher类进行实例化,不过这里是按照computed-watcher的形式。源码在
src/core/observer/watcher.js
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
|
constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm vm._watchers.push(this) if (options) { this.lazy = !!options.lazy } this.dirty = this.lazy if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.value = this.lazy ? undefined : this.get() }
|
- vue 实例中就定义计算属性,让computed成为一个响应式数据,并定义它的get属性。
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
|
const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } export function defineComputed( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } Object.defineProperty(target, key, sharedPropertyDefinition) }
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 } } }
|
- 订阅者触发更新
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
| export default class Dep { depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
evaluate () { this.value = this.get() this.dirty = false }
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } return value }
|
分析完源码总结一下流程
- 初始化
computed
调用 initComputed
函数
- 注册
watcher
实例,让计算属性成为其他 watcher
的消息订阅器的订阅者
- 定义计算属性
Object.defineProperty
的get
访问器函数,进而调用watcher
的evaluate
来获取computed的值
- 当某个属性发生变化,触发
set
拦截函数,然后调用自身消息订阅器 dep
的 notify
方法,遍历当前 dep
中保存着所有订阅者并逐个调用 watcher
的 update
方法,完成响应更新。