JavaScript深入 — Object.defineProperty 与 Proxy
在 JavaScript 中,Object.defineProperty
和 Proxy
是两种强大的机制,用于拦截和处理对象属性的读写操作。Vue.js 的响应式系统正是基于它们实现的。下面将详细介绍这两者的基础用法、区别以及它们在 Vue 响应式系统中的应用。
Object.defineProperty
基本用法
Object.defineProperty
允许在对象上定义新的属性或修改现有属性,并控制属性的行为。
👇语法如下:
Object.defineProperty(obj, prop, descriptor);
其中,descriptor
是一个描述符对象,包含以下两类属性:
数据描述符(Data Descriptor):
value
:属性的值,默认为undefined
writable
:属性是否可修改,默认为false
enumerable
:属性是否可枚举(可被for...in
或Object.keys()
访问),默认为false
configurable
:属性描述符是否可被修改,且属性是否可删除,默认为false
访问器描述符(Accessor Descriptor):
get
:属性的getter
函数set
:属性的setter
函数
注意:两类描述符不能同时存在。
示例代码
let obj = {};
Object.defineProperty(obj, 'name', {
value: 'Simon',
writable: false, // 不能修改
enumerable: true, // 可枚举
configurable: false // 不能删除或重新配置
});
console.log(obj.name); // Simon
obj.name = 'Rita';
console.log(obj.name); // 仍然是 Simon(因为 `writable: false`)
在 Vue2 响应式系统中的应用
Vue 2 通过 Object.defineProperty
实现响应式数据绑定,即在 getter
和 setter
中拦截数据读取和修改,并触发视图更新。
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`Get ${key}: ${val}`);
return val;
},
set(newVal) {
console.log(`Set ${key}: ${newVal}`);
if (val !== newVal) {
val = newVal;
// 这里可以添加视图更新逻辑
}
}
});
}
let data = { message: 'Hello, Simon!' };
defineReactive(data, 'message', data.message);
data.message = 'Hello, Vue!'; // Set message: Hello, Vue!
console.log(data.message); // Get message: Hello, Vue!
Proxy
基本介绍
Proxy
是 ES6 引入的功能,它允许创建一个代理对象来拦截对目标对象的操作,从而增强或修改对象的行为。
const proxy = new Proxy(target, handler);
target
:被代理的目标对象handler
:定义拦截行为的对象
常用拦截方法
get(target, property, receiver)
:拦截读取属性set(target, property, value, receiver)
:拦截设置属性has(target, property)
:拦截in
操作符deleteProperty(target, property)
:拦截delete
操作apply(target, thisArg, argumentsList)
:拦截函数调用construct(target, argumentsList, newTarget)
:拦截new
操作符
示例代码
const handler = {
get(target, prop) {
console.log(`Get ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`Set ${prop} to ${value}`);
target[prop] = value;
return true;
}
};
let obj = { name: 'John' };
let proxyObj = new Proxy(obj, handler);
console.log(proxyObj.name); // Get name
proxyObj.name = 'Byron'; // Set name to Byron
Reflect
Reflect
是 ES6 引入的一个全局对象,提供了与对象操作相关的静态方法,主要用于搭配 Proxy
处理默认行为。
const handler = {
get(target, prop, receiver) {
console.log(`Get ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Set ${prop} to ${value}`);
return Reflect.set(target, prop, value, receiver);
}
};
Vue2 vs Vue3 响应式的变化
Vue 2 使用 Object.defineProperty
,但它有以下局限性:
- 只能拦截已存在的属性,无法监听新增属性
- 不能检测数组索引变化
- 需要对每个属性单独定义
getter
和setter
Vue 3 改用 Proxy
,带来了以下优化:
- 直接监听整个对象,包括属性的新增和删除
- 数组索引和
length
变化可以被检测 - 更灵活强大的拦截系统
Vue3 响应式示例
const reactiveHandler = {
get(target, prop) {
console.log(`Get ${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`Set ${prop} to ${value}`);
return Reflect.set(target, prop, value);
}
};
const reactiveData = new Proxy({ message: 'Hello' }, reactiveHandler);
reactiveData.message = 'Hello, Vue 3!'; // Set message to Hello, Vue 3!
console.log(reactiveData.message); // Get message
结论
Object.defineProperty
适用于简单的数据拦截,但有一定的局限性。Proxy
提供更强大的拦截能力,能监听对象的新增、删除及数组索引变化。- Vue 2 使用
Object.defineProperty
作为响应式系统的基础,而 Vue 3 改用Proxy
以解决 Vue 2 的限制。
希望这篇文章能帮助你理解 Object.defineProperty
和 Proxy
的核心概念及它们在 Vue 响应式系统中的应用!