26. Object.defineProperty 和 Proxy 用法
Object.defineProperty
和 Proxy
都是 JavaScript 中用于实现数据劫持和响应式系统的重要 API,它们在 Vue 等前端框架中被广泛使用。
Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
基本语法
Object.defineProperty(obj, prop, descriptor);
obj
: 要定义属性的对象prop
: 要定义或修改的属性名称descriptor
: 要定义或修改的属性描述符
属性描述符
const obj = {};
Object.defineProperty(obj, "name", {value: "John", // 属性值writable: true, // 是否可写enumerable: true, // 是否可枚举configurable: true, // 是否可配置// 或者使用 getter 和 setterget() {return this._name;},set(value) {this._name = value;},
});
响应式实现示例
function reactive(obj) {const observed = {};Object.keys(obj).forEach((key) => {let value = obj[key];Object.defineProperty(observed, key, {get() {console.log(`获取 ${key}: ${value}`);return value;},set(newValue) {console.log(`设置 ${key}: ${newValue}`);value = newValue;},enumerable: true,configurable: true,});});return observed;
}const data = reactive({ name: "John", age: 25 });
console.log(data.name); // 获取 name: John
data.name = "Jane"; // 设置 name: Jane
Object.defineProperty 的局限性
- 无法监听数组索引的变化
- 无法监听对象属性的添加和删除
- 必须遍历对象的每个属性进行劫持
- 无法劫持嵌套对象
Proxy
Proxy
对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义。
基本语法
const proxy = new Proxy(target, handler);
target
: 要代理的目标对象handler
: 包含拦截方法的对象
常用拦截操作
const target = {name: "John",age: 25,
};const proxy = new Proxy(target, {// 拦截属性读取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);},// 拦截 in 操作符has(target, prop) {console.log(`检查属性存在: ${prop}`);return Reflect.has(target, prop);},// 拦截 delete 操作deleteProperty(target, prop) {console.log(`删除属性: ${prop}`);return Reflect.deleteProperty(target, prop);},
});console.log(proxy.name); // 读取属性: name
proxy.age = 30; // 设置属性 age: 30
"name" in proxy; // 检查属性存在: name
delete proxy.age; // 删除属性: age
响应式实现示例
function reactive(target) {if (typeof target !== "object" || target === null) {return target;}const handler = {get(target, key, receiver) {console.log(`获取属性: ${key}`);const result = Reflect.get(target, key, receiver);// 递归处理嵌套对象return typeof result === "object" ? reactive(result) : result;},set(target, key, value, receiver) {console.log(`设置属性 ${key}: ${value}`);const result = Reflect.set(target, key, value, receiver);return result;},};return new Proxy(target, handler);
}const data = reactive({name: "John",age: 25,hobbies: ["reading", "coding"],
});console.log(data.name); // 获取属性: name
data.hobbies.push("swimming"); // 获取属性: hobbies
两者对比
特性 | Object.defineProperty | Proxy |
---|---|---|
兼容性 | ES5,兼容性好 | ES6,需要现代浏览器支持 |
拦截能力 | 有限,只能拦截特定属性 | 强大,可拦截 13 种操作 |
数组监听 | 无法直接监听数组索引 | 可以完美监听数组变化 |
嵌套对象 | 需要递归处理 | 可以通过 get 递归处理 |
性能 | 直接修改属性性能较好 | 通过代理有一定性能损耗 |
使用复杂度 | 需要遍历每个属性 | 一次代理整个对象 |
在 Vue 中的应用
- Vue 2: 使用
Object.defineProperty
实现响应式系统 - Vue 3: 使用
Proxy
重构响应式系统,解决了 Vue 2 的诸多限制
// Vue 2 的限制示例
const vm = new Vue({data: {items: [1, 2, 3],},
});// 以下操作在 Vue 2 中无法被检测到
vm.items[0] = 2; // 通过索引设置数组项
vm.items.length = 0; // 修改数组长度
vm.newProperty = "value"; // 添加新属性// 需要使用 Vue 提供的 API
// Vue.set(vm.items, 0, 2) 或 vm.$set(vm.items, 0, 2)
// vm.items.splice(0)// Vue 3 中使用 Proxy 没有这些限制