浅谈 Vue2 中 computed 的工作原理
浅谈 Vue2 中 computed 的工作原理
通过实现一个简化版的 Vue computed,深入理解其响应式原理。
📌 前言
Vue 的 computed 计算属性是日常开发中的常用功能,但它是如何工作的?本文将通过手写一个简化版实现,带你深入理解 computed 的核心原理。
一、理解 Object.defineProperty
首先,我们需要了解 Object.defineProperty 这个核心 API:
const person = {};
Object.defineProperty(person, 'age', {
get: function() {
console.log('person get age');
return 18;
}
});
console.log('This person is', person.age);
// 输出:person get age → This person is 18
关键点:访问 person.age 时,实际执行的是 get 函数,而非直接读取属性值。这为响应式系统奠定了基础。
二、实现基础响应式系统
Vue 内部通过类似方式将普通对象转化为响应式对象:
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get: function() {
return val;
},
set: function(newValue) {
val = newValue;
}
});
}
const person = {};
defineReactive(person, 'age', 25);
defineReactive(person, 'country', 'Brazil');
// 正常使用
if (person.age < 18) {
console.log('small');
} else {
console.log('big'); // 输出:big
}
// 响应式更新
person.country = 'Russia'; // val 被更新
核心思想:person.age 并不直接存储值,而是通过闭包中的 val 变量和 getter/setter 函数来管理。
三、实现计算属性
基于响应式系统,我们可以实现一个简化版的 defineComputed:
function defineComputed(obj, key, computeFunc, updateCallback) {
Object.defineProperty(obj, key, {
get: function() {
return computeFunc();
},
set: function(newValue) {
// 暂不处理
}
});
}
// 使用示例
defineComputed(
person,
'status',
function() {
if (person.age > 18) {
return 'big';
}
return 'small';
},
function(newValue) {
console.log('status has changed to', newValue);
}
);
⚠️ 存在的问题
上述实现有两个缺陷:
- 重复计算:每次访问计算属性都会执行
computeFunc() - 无法响应依赖变化:当
person.age更新时,person.status不会自动更新
四、引入依赖收集机制
为解决上述问题,我们需要一个依赖收集器 Dep:
const Dep = {
target: null // 当前正在执行的计算属性回调
};
4.1 更新 defineComputed
function defineComputed(obj, key, computeFunc, updateCallback) {
Object.defineProperty(obj, key, {
get: function() {
Dep.target = onDependencyUpdated;
const value = computeFunc(); // 触发所有依赖属性的 get
Dep.target = null;
return value;
},
set: function(newValue) {
updateCallback(newValue);
}
});
}
4.2 更新 defineReactive
在响应式属性的 getter 中收集依赖:
function defineReactive(obj, key, val) {
const deps = []; // 存储依赖此属性的所有计算属性回调
Object.defineProperty(obj, key, {
get: function() {
if (Dep.target) {
deps.push(Dep.target); // 收集依赖
}
return val;
},
set: function(newValue) {
val = newValue;
deps.forEach(computedCallback => computedCallback()); // 通知所有依赖更新
}
});
}
4.3 实现依赖更新回调
function onDependencyUpdated() {
const value = this?.computeFunc();
this?.updateCallback(value);
}
function defineComputed(obj, key, computeFunc, updateCallback) {
Object.defineProperty(obj, key, {
get: function() {
Dep.target = onDependencyUpdated.bind({
__key__: key,
computeFunc,
updateCallback
});
const value = computeFunc();
Dep.target = null;
return value;
},
set: function(newValue) {
updateCallback(newValue);
}
});
}
五、完整实现代码
const Dep = {
target: null
};
function defineReactive(obj, key, val) {
const deps = [];
Object.defineProperty(obj, key, {
get: function() {
if (Dep.target) {
deps.push(Dep.target);
}
return val;
},
set: function(newValue) {
val = newValue;
deps.forEach(computedCallback => computedCallback());
}
});
}
function onDependencyUpdated() {
const value = this?.computeFunc();
this?.updateCallback(value);
}
function defineComputed(obj, key, computeFunc, updateCallback) {
Object.defineProperty(obj, key, {
get: function() {
Dep.target = onDependencyUpdated.bind({
__key__: key,
computeFunc,
updateCallback
});
const value = computeFunc();
Dep.target = null;
return value;
},
set: function(newValue) {
updateCallback(newValue);
}
});
}
// ===== 使用示例 =====
const person = {};
defineReactive(person, 'age', 17);
defineReactive(person, 'country', 'Brazil');
defineComputed(
person,
'status',
function() {
if (person.age > 18) {
return 'big';
}
return 'small';
},
function(newValue) {
console.log('status has changed to', newValue);
}
);
console.log(person.status); // 输出:small
person.age = 22; // 自动触发:status has changed to big
六、核心原理总结
| 概念 | 作用 |
|---|---|
Object.defineProperty |
拦截属性的 get/set 操作 |
Dep.target |
全局标记当前正在执行的计算属性回调 |
deps 数组 |
存储依赖某响应式属性的所有计算属性回调 |
| 依赖收集 | 在 getter 中将 Dep.target 推入 deps 数组 |
| 派发更新 | 在 setter 中遍历 deps 数组,执行所有回调 |
评论 (0)