Computed 计算属性是 Vue 中常用的一个功能,我们今天来说一下他的执行过长
拿官网简单的例子来看一下:
<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 getterreversedMessage: function () {// `this` points to the vm instancereturn this.message.split('').reverse().join('')}}
})
Vue 源码分析 Computed 的实现原理
data 属性初始化 getter setter:
// 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 实例。
// src/core/observer/watcher.js// 当通过 vm.reversedMessage 获取计算属性时,就会进到这个 getter 方法 get () {// this 指的是 watcher 实例// 将当前 watcher 实例暂存到 Dep.target,这就表示开启了依赖收集任务pushTarget(this)let valueconst vm = this.vmtry {// 在执行 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 的代码短小精悍,但却承担着非常重要的依赖收集环节。
// 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.