前言
在 Vue2 和 Vue3 的响应式原理中,我们经常会听到"数据劫持"和"数据代理"这两个概念。它们分别对应着 Object.defineProperty
和 Proxy
这两种技术实现。本文将彻底解析它们的区别,通过代码示例、参数说明和使用场景对比,让你完全掌握这两种技术。
一、基本概念对比
特性 | Object.defineProperty | Proxy |
---|---|---|
ES版本 | ES5 | ES6 |
拦截维度 | 属性级别 | 对象级别 |
数组处理 | 需要特殊处理 | 原生支持 |
新增属性 | 需要手动监听 | 自动拦截 |
性能 | 较好 | 更优 |
二、Object.defineProperty(数据劫持)
1. 基本用法
js
const obj = {};
let value = '初始值';
Object.defineProperty(obj, 'property', {
get: function() {
console.log('获取值');
return value;
},
set: function(newValue) {
console.log('设置值', newValue);
value = newValue;
}
});
obj.property; // 控制台输出"获取值"
obj.property = '新值'; // 控制台输出"设置值 新值"
2. 参数详解
js
Object.defineProperty(obj, prop, descriptor)
-
obj
:目标对象 -
prop
:要定义或修改的属性名 -
descriptor
:属性描述符,包含:configurable
:是否可删除,默认为 falseenumerable
:是否可枚举,默认为 falsevalue
:属性值writable
:是否可写get
:getter 函数set
:setter 函数
3. 实现简单响应式
js
function observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
let value = data[key];
observe(value); // 递归子属性
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log(`获取 ${key}: ${value}`);
return value;
},
set(newVal) {
console.log(`设置 ${key}: ${newVal}`);
if (value !== newVal) {
value = newVal;
observe(newVal); // 新值是对象时继续监听
}
}
});
});
}
const data = { name: '张三', info: { age: 20 } };
observe(data);
data.name; // 获取 name: 张三
data.info.age = 21; // 获取 info: [object Object] (注意:无法直接监听嵌套属性变化)
4. 局限性
- 无法检测对象属性的添加或删除
js
const obj = { a: 1 };
observe(obj);
obj.b = 2; // 无法监听新增属性
- 数组方法需要特殊处理
js
const arr = [1, 2, 3];
observe(arr);
arr.push(4); // 无法监听数组push操作
- 需要递归监听属性造成额外的性能开销
三、Proxy(数据代理)
1. 基本用法
js
const target = {};
const handler = {
get(target, prop, receiver) {
console.log(`获取属性 ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`设置属性 ${prop} 为 ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name = '李四'; // 设置属性 name 为 李四
console.log(proxy.name); // 获取属性 name → 李四
2. 参数详解
js
const proxy = new Proxy(target, handler)
-
target
:目标对象 -
handler
:处理器对象,包含"陷阱"方法:get(target, prop, receiver)
:拦截属性读取set(target, prop, value, receiver)
:拦截属性设置has(target, prop)
:拦截in操作符deleteProperty(target, prop)
:拦截delete操作- 等13种拦截操作
3. 实现完整响应式
js
function reactive(data) {
if (!data || typeof data !== 'object') return data;
const handler = {
get(target, key, receiver) {
console.log(`获取 ${key}`);
const result = Reflect.get(target, key, receiver);
return reactive(result); // 递归代理嵌套对象
},
set(target, key, value, receiver) {
console.log(`设置 ${key} 为 ${value}`);
return Reflect.set(target, key, reactive(value), receiver);
},
deleteProperty(target, key) {
console.log(`删除 ${key}`);
return Reflect.deleteProperty(target, key);
}
};
return new Proxy(data, handler);
}
const state = reactive({
name: '王五',
hobbies: ['篮球', '游泳']
});
state.hobbies.push('跑步');
// 获取 hobbies → 设置 2 为 跑步 → 设置 length 为 3
4. 优势体现
- 完美监听数组变化
js
const arr = reactive([1, 2, 3]);
arr.push(4); // 能够正常拦截
- 自动监听新增属性
js
const obj = reactive({});
obj.newProp = '新属性'; // 能够在set中正常拦截
- 支持更多拦截操作
js
const proxy = reactive({});
delete proxy.name; // 可以拦截delete操作
- 无需递归遍历自身进行数据劫持
四、核心区别对比
1. 实现原理不同
- Object.defineProperty:直接修改原对象属性描述符
- Proxy:创建原对象的代理对象,不修改原对象
2. 拦截粒度不同
js
// Object.defineProperty必须指定具体属性
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {...});
});
// Proxy一次性拦截整个对象
new Proxy(obj, {
get() {...},
set() {...}
});
五、Vue2 和 Vue3 响应式原理对比
Vue2 实现:
js
// 简化版Vue2响应式原理
class Observer {
constructor(value) {
this.walk(value);
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
});
}
}
function defineReactive(obj, key, val) {
// 递归处理子属性
observe(val);
Object.defineProperty(obj, key, {
get() {
console.log(`获取 ${key}`);
return val;
},
set(newVal) {
if (val === newVal) return;
console.log(`设置 ${key}`);
val = newVal;
observe(newVal);
}
});
}
Vue3 实现:
js
// 简化版Vue3响应式原理
function reactive(target) {
return createReactiveObject(target);
}
function createReactiveObject(target) {
const handler = {
get(target, key, receiver) {
track(target, key); // 依赖收集
const result = Reflect.get(target, key, receiver);
return isObject(result) ? reactive(result) : result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key); // 触发更新
}
return result;
}
// 其他陷阱...
};
return new Proxy(target, handler);
}
七、总结
Vue3 改用 Proxy 实现响应式,解决了 Vue2 中的诸多限制,降低我们的心智负担
希望这篇博文能帮助你彻底理解 Proxy 和 Object.defineProperty 的区别!如果有任何问题,欢迎在评论区留言讨论。