Computed 计算属性是 Vue 中经常使用的一个功能,咱们今天来讲一下他的执行过长react
拿官网简单的例子来看一下:express
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div> var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { // a computed getter reversedMessage: function () { // `this` points to the vm instance return this.message.split('').reverse().join('') } } })
data 属性初始化 getter setter:ide
// src/observer/index.js // 这里开始转换 data 的 getter setter,原始值已存入到 __ob__ 属性中 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 判断是否处于依赖收集状态 if (Dep.target) { // 创建依赖关系 dep.depend() ... } return value }, set: function reactiveSetter (newVal) { ... // 依赖发生变化,通知到计算属性从新计算 dep.notify() } })
computed 计算属性初始化函数
// src/core/instance/state.js // 初始化计算属性 function initComputed (vm: Component, computed: Object) { ... // 遍历 computed 计算属性 for (const key in computed) { ... // 建立 Watcher 实例 // create internal watcher for the computed property. watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions) // 建立属性 vm.reversedMessage,并将提供的函数将用做属性 vm.reversedMessage 的 getter, // 最终 computed 与 data 会一块儿混合到 vm 下,因此当 computed 与 data 存在重名属性时会抛出警告 defineComputed(vm, key, userDef) ... } } export function defineComputed (target: any, key: string, userDef: Object | Function) { ... // 建立 get set 方法 sharedPropertyDefinition.get = createComputedGetter(key) sharedPropertyDefinition.set = noop ... // 建立属性 vm.reversedMessage,并初始化 getter setter 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 方法用于取值操做 watcher.evaluate() } // 同第1步,判断是否处于依赖收集状态 if (Dep.target) { watcher.depend() } return watcher.value } } }
不管是属性仍是计算属性,都会生成一个对应的 watcher 实例。oop
// src/core/observer/watcher.js // 当经过 vm.reversedMessage 获取计算属性时,就会进到这个 getter 方法 get () { // this 指的是 watcher 实例 // 将当前 watcher 实例暂存到 Dep.target,这就表示开启了依赖收集任务 pushTarget(this) let value const vm = this.vm try { // 在执行 vm.reversedMessage 的函调函数时,会触发属性(步骤1)和计算属性(步骤2)的 getter // 在这个执行过程当中,就能够收集到 vm.reversedMessage 的依赖了 value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { if (this.deep) { traverse(value) } // 结束依赖收集任务 popTarget() this.cleanupDeps() } return value }
上面多出提到了 dep.depend, dep.notify, Dep.target,那么 Dep 到底是什么呢?源码分析
Dep 的代码短小精悍,但却承担着很是重要的依赖收集环节。ui
// src/core/observer/dep.js export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { // 更新 watcher 的值,与 watcher.evaluate() 相似, // 但 update 是给依赖变化时使用的,包含对 watch 的处理 subs[i].update() } } } // 当首次计算 computed 属性的值时,Dep 将会在计算期间对依赖进行收集 Dep.target = null const targetStack = [] export function pushTarget (_target: Watcher) { // 在一次依赖收集期间,若是有其余依赖收集任务开始(好比:当前 computed 计算属性嵌套其余 computed 计算属性), // 那么将会把当前 target 暂存到 targetStack,先进行其余 target 的依赖收集, if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { // 当嵌套的依赖收集任务完成后,将 target 恢复为上一层的 Watcher,并继续作依赖收集 Dep.target = targetStack.pop() }
1. data 属性初始化 getter setterthis
2. computed 计算属性初始化,提供的函数将用做属性 vm.reversedMessage 的 getterlua
3. 当首次获取 reversedMessage 计算属性的值时,Dep 开始依赖收集server
4. 在执行 message getter 方法时,若是 Dep 处于依赖收集状态,则断定 message 为 reversedMessage 的依赖,并创建依赖关系
5. 当 message 发生变化时,根据依赖关系,触发 reverseMessage 的从新计算