这是一个简单的 JavaScript 数据响应式 实现示例,使用了 Object.defineProperty
来拦截属性的读取和设置操作。下面是对代码的逐行解析:
代码分析
javascript
function observe(obj) {
for (const key in obj) {
let internalValue = obj[key];
Object.defineProperty(obj, key, {
get: function () {
// 记录:是哪个函数在用我
return internalValue;
},
set: function (val) {
internalValue = val;
// 运行:执行用我的函数
},
});
}
}
1. 函数目的
- 将传入的对象
obj
转换为响应式对象:当属性被读取或修改时,可以执行额外操作(如依赖收集、触发更新)。
2. 遍历对象属性
javascript
for (const key in obj) {
- 遍历
obj
的所有可枚举属性。
3. 闭包保存属性值
javascript
let internalValue = obj[key];
- 用局部变量
internalValue
保存属性当前值,避免在 getter/setter 中直接使用obj[key]
导致递归调用。
4. 使用 Object.defineProperty
重定义属性
javascript
Object.defineProperty(obj, key, { ... });
- 将每个属性转为 getter/setter,实现拦截。
5. Getter
javascript
get: function () {
// 记录:是哪个函数在用我
return internalValue;
}
- 当属性被读取时,返回
internalValue
。 - 注释提示这里可以用于依赖收集(例如 Vue 2 中记录当前正在执行的渲染函数或计算属性)。
6. Setter
javascript
set: function (val) {
internalValue = val;
// 运行:执行用我的函数
}
- 当属性被修改时,更新
internalValue
。 - 注释提示这里可以用于触发更新(例如通知所有依赖该属性的函数重新执行)。
存在的问题
- 只能拦截已有属性 :如果后续给对象新增属性,不会被响应式处理(Vue 2 中需用
Vue.set
)。 - 数组变异方法不支持 :如
push
、pop
等不会触发 setter(Vue 2 中通过重写数组方法解决)。 - 性能问题:遍历所有属性并重定义,对于大对象或频繁操作可能影响性能。
- 嵌套对象未处理 :如果属性值是对象,需要递归调用
observe
才能实现深层响应式。
改进示例(支持深层响应式)
javascript
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return;
for (const key in obj) {
let internalValue = obj[key];
observe(internalValue); // 递归处理嵌套对象
Object.defineProperty(obj, key, {
get: function () {
console.log(`读取 ${key}: ${internalValue}`);
return internalValue;
},
set: function (val) {
console.log(`设置 ${key} 为 ${val}`);
internalValue = val;
observe(val); // 新值是对象时也转为响应式
},
});
}
}