Vue中computed(计算属性)的简单原理分析
仅供学习交流,有错误可以评论区指出~
一、什么是 computed 及其用法
computed(计算属性)用于根据已有的响应式数据,声明式地生成新的“派生”数据,并且 Vue 会对其结果做缓存,只有在依赖发生变化时才重新计算。
1. 选项式 API 中的用法
<template>
<div>
<p>原始消息:{{ message }}</p>
<p>反转消息:{{ reversedMessage }}</p>
<input v-model="message" placeholder="输入一些文字">
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!'
};
},
computed: {
// 定义一个计算属性 reversedMessage
reversedMessage() {
return this.message.split('').reverse().join('');
}
}
};
</script>
效果:reversedMessage 会根据 message 自动更新,并且只在 message 变化时才重新执行一次分割与拼接操作。
2. 组合式 API 中的用法
<template>
<div>
<p>原始计数:{{ count }}</p>
<p>双倍计数:{{ double }}</p>
<button @click="count++">+1</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const count = ref(0);
// computed 用法与选项式 API 类似
const double = computed(() => count.value * 2);
</script>
二、computed 的实现原理
说在前面,computed 变化要明确2个步骤,一个是依赖是否变化,一个是computed属性的读取。
Vue(以 2.x 为例,3.x 源码思路相似)是基于 响应式系统 + 依赖收集 + watcher 来实现 computed 的。
1. 在 beforeCreate 钩子中初始化
-
遍历
computed配置
在组件实例化时(beforeCreate阶段),Vue 会遍历this.$options.computed中的每个属性。 -
为每个计算属性创建一个专属的 Watcher(computed watcher),lazy为true表示这个 watcher 是“懒执行”的,不会立即调用getter收集依赖,初始时也不会调用
getter,只有在读取时会调用,见第3步。
注意:每个计算属性的 Watcher(computed watcher) 会维护两个核心状态:
-
dirty(脏标记,初始为true)
指示当前缓存值是否被更改,dirty = true时表示需要重新计算,在下一次读取computed属性时重新计算(调用getter)。 -
value
用来存储上一次计算的结果。
// 伪代码示意
for (let key in computedOptions) {
const userDef = computedOptions[key]
const getter = typeof userDef === 'function'
? userDef
: userDef.get
// 创建一个 lazy watcher,不立刻执行
watchers[key] = new Watcher(vm, getter, /* cb */ null, { lazy: true })
// 在实例上代理访问:vm[key] → vm._computedWatchers[key].evaluate()
defineComputed(vm, key, watchers[key])
}
2.判断依赖是否变化
-
响应式数据变化通知依赖
每个响应式数据在值发生改变时,会调用自身的 setter,然后通过dep.notify()通知所有收集到的 Watcher,包括计算属性的 Watcher。 -
Watcher 的响应处理
在所有被通知的 Watcher 中,计算属性的 Watcher 会调用其update()方法,内部逻辑类似:
Watcher.prototype.update = function() {
// 如果是 computed watcher(lazy 为 true),仅标记为 dirty
if (this.lazy) {
this.dirty = true;
} else {
// 如果是渲染 watcher,则加入更新队列等待组件重新渲染
queueWatcher(this);
}
};
-
计算属性 Watcher:由于其
lazy属性为true,所以在依赖数据变化时,只需将内部的dirty标记设置为true,表明下次读取计算属性时需要重新计算。
这种设计保证了计算属性不会在依赖变化时立即重新计算,而是通过 dirty 标记来实现“惰性求值”:只有当计算属性被访问(例如模板渲染或者组件方法中调用)时,才会检查 dirty 状态,并在必要时执行重新计算。
3. 读取计算属性时:依赖收集 + 缓存
无论第1步还是第2步我们都将处理好dirty的值,要么是true要么是false,现在我们可能需要调用computed属性了,这时会检查dirty的值,如果为true就需要更新,这就需要用代理的getter进行计算属性的依赖收集,进行重新计算。
当我们在模板或代码中读取 计算属性 时,实际调用的是代理的 getter,其内部逻辑类似:
Watcher.prototype.evaluate = function() {
if (this.dirty) {
// 1. 重新执行用户的 getter
this.value = this.get();
// 2. 依赖收集:在执行 getter 时,
// 访问的所有响应式属性都会把当前 watcher 加入它们的 Dep
this.dirty = false;
}
return this.value;
};
function defineComputed(target, key, watcher) {
Object.defineProperty(target, key, {
get: function computedGetter() {
// 触发依赖收集,如果在渲染 watcher 中读取了 computed,
// 渲染 watcher 也会被收集,保证渲染时的更新
if (Dep.target) {
watcher.depend();
}
// 计算或直接返回缓存
return watcher.evaluate();
},
set: function computedSetter(newVal) {
// 如果用户提供了 setter,就调用;否则发出警告
watcher.userSetter && watcher.userSetter(newVal);
}
});
}
-
首次读取:首次读取计算属性 →发现
dirty === true→ 调用getter计算、收集依赖、缓存结果、dirty = false→ 返回value。 -
后续读取(依赖未变):依赖不改变→读取计算属性 →发现
dirty === false→ 直接返回缓存的value,不重复计算。 -
后续读取(依赖改变):依赖改变→计算属性的watcher将dirty赋值为true→读取计算属性(未来需要使用的时候读取) →发现
dirty === true→getter收集依赖更新value。
三、(补充)在模板渲染和改变过程中computed实现
在讲解 模板渲染和改变过程中computed的实现原理之前,先明确一个概念:
当模板(页面)中响应式数据发生变化时,Vue 会涉及两种不同的 Watcher:
-
渲染 Watcher(render watcher):负责收集并监听模板中访问的所有响应式依赖,依赖变化时触发组件的重新渲染。
-
计算属性 Watcher(computed watcher):负责收集并监听计算属性内部访问的响应式依赖,支持懒计算和结果缓存。
需要特别注意:
-
首次计算:在组件初次渲染阶段,渲染 Watcher 执行渲染逻辑时会访问计算属性,因为一开始dirty值为true,从而触发计算属性 Watcher 的 getter 进行首次求值,然后dirty被置为false。
-
后续计算:当依赖变化后,渲染watcher和computed watcher都收到通知,此时渲染watcher直接将DOM的渲染加入队列等待渲染,而computed watcher只会将computed的dirty切换维true,并不做其他操作,等到渲染 Watcher 再次访问该计算属性,才会触发计算属性 Watcher 的 getter 重新计算。
换句话说,计算属性的所有求值操作(包括初始化和依赖更新后的重新计算)都是在渲染 Watcher 执行渲染过程中被调用的。这样既保证了渲染的同步性,又通过计算属性 Watcher 的 lazy + dirty 机制实现了惰性求值和缓存。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)