1. Object.defineProperty 的实现原理与限制
基本原理
Object.defineProperty 允许我们为对象的某个属性添加 getter 和 setter,当属性值被访问或修改时,可以拦截这些操作:
get:在访问属性时触发,通常用于依赖收集。set:在修改属性时触发,通常用于派发更新。
以下是一个用 Object.defineProperty 实现简单响应式的示例:
function defineReactive(obj, key, value) {
// 深度递归,确保子对象也能响应式
if (typeof value === 'object' && value !== null) {
observe(value);
}
// 定义 getter 和 setter
Object.defineProperty(obj, key, {
get() {
console.log(`访问了属性 ${key}`);
return value;
},
set(newValue) {
if (newValue !== value) {
console.log(`属性 ${key} 被修改为 ${newValue}`);
value = newValue;
// 如果新值是对象,则需要递归响应式
if (typeof newValue === 'object' && newValue !== null) {
observe(newValue);
}
}
},
});
}
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return;
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}
// 测试
const data = { name: 'Vue', info: { version: '2.0' } };
observe(data);
data.name; // 访问了属性 name
data.name = 'Vue.js'; // 属性 name 被修改为 Vue.js
data.info.version; // 访问了属性 version
data.info.version = '2.1'; // 属性 version 被修改为 2.1
局限性
- 对象新增/删除属性不能被监听:
Object.defineProperty只能作用于已存在的属性,新增属性必须使用Vue.set。 - 数组操作的监听不足:对数组的方法(如
push、splice等)无法直接拦截,Vue 2 通过覆盖数组原型方法解决。 - 需要递归遍历:深层嵌套对象需要递归遍历,每层属性都需要用
Object.defineProperty包装,性能不佳。
Proxy 的实现原理与优势
基本原理
Proxy 是 ES6 引入的新特性,允许我们定义一个对象的基本操作行为(如读取、写入、删除等)的自定义逻辑,而不需要手动定义每个属性的 getter 和 setter。
- 可以直接监听整个对象(包括新增/删除属性)。
- 支持多种拦截操作(如
get、set、deleteProperty等)。
以下是使用 Proxy 实现响应式的简单示例:
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
const value = target[key];
console.log(`访问了属性 ${key}`);
// 如果是对象,递归响应式
return typeof value === 'object' && value !== null ? reactive(value) : value;
},
set(target, key, value) {
console.log(`属性 ${key} 被修改为 ${value}`);
target[key] = value;
// 可以在这里触发更新
return true;
},
deleteProperty(target, key) {
console.log(`属性 ${key} 被删除`);
delete target[key];
// 可以在这里触发更新
return true;
}
});
}
// 测试
const data = reactive({ name: 'Vue', info: { version: '3.0' } });
data.name; // 访问了属性 name
data.name = 'Vue.js'; // 属性 name 被修改为 Vue.js
data.info.version; // 访问了属性 version
data.info.version = '3.1'; // 属性 version 被修改为 3.1
data.info.newKey = '新属性'; // 属性 newKey 被修改为 新属性
delete data.info.version; // 属性 version 被删除
优势
- 可以监听对象的新增/删除操作:不需要额外处理新增属性的问题。
- 无需递归:
Proxy可以对整个对象的操作进行拦截,只有在访问时才递归处理子对象。 - 功能更强大:可以拦截更多的操作,比如数组的索引访问、
in操作符等。
Vue 的响应式实现对比
| 特性 | Object.defineProperty (Vue 2) | Proxy (Vue 3) |
|---|---|---|
| 新增/删除属性监听 | 不支持,需手动处理 | 原生支持 |
| 深度监听 | 需要递归处理 | 按需递归,性能更优 |
| 数组操作监听 | 覆盖原型方法实现 | 原生支持 |
| API 灵活性 | 较差 | 更强大 |
| 浏览器支持 | 较好 | 需要现代浏览器支持 |
总结
- 在 Vue 2 中,
Object.defineProperty是响应式系统的核心,但存在一些局限性,比如无法监听新增/删除属性,深度监听性能较差。 - Vue 3 使用了
Proxy重新实现响应式系统,解决了 Vue 2 的诸多问题,同时性能更好,代码更简洁。
这也是为什么 Vue 3 推荐使用现代浏览器,并将响应式设计从底层进行了重构的原因。