Object.defineProperty
1. Object.defineProperty 可以在对象上定义新属性或修改已有属性,并精确控制这些属性的行为
Object.defineProperty(object, key, descriptor)
- object: 需要定义属性的对象
- key: 定义的属性名
- descriptor: 属性描述符
2. descriptor 属性描述符的值
- value: 设置属性的值
- writable: 值是否可以重写
- set: 目标属性设置
- get: 目标属性获取
- enumerable: 目标属性是否可以被枚举(是否可遍历)
- configurable: 目标属性是否可以被删除或是否可以再次修改特性
定义一个不可变的属性:
const obj = {};
Object.defineProperty(obj, 'constant', {
value: 'I am constant',
writable: false,
});
console.log(obj.constant); // 输出: I am constant
// 尝试修改该属性将不会有效果,并在严格模式下抛出错误
obj.constant = 'I want to change'; // 在非严格模式下无效,在严格模式下抛出错误
定义一个带有getter和setter的属性:
const person = {
firstName: 'John',
lastName: 'Doe'
};
Object.defineProperty(person, 'fullName', {
get() {
return this.firstName + ' ' + this.lastName;
},
set(value) {
[this.firstName, this.lastName] = value.split(' ');
}
});
console.log(person.fullName); // John Doe
person.fullName = 'Jane Doe';
console.log(person.firstName); // Jane
console.log(person.lastName); // Doe
注意事项
- 默认值:如果属性描述符中省略了 writable、enumerable 和 configurable 这三个属性,它们的默认值都是 false。这意味着如果没有明确指定,属性将不可写、不可枚举且不可配置。
- 不可变的属性:一旦属性被定义为 writable: false,你就不能再更改该属性的值,除非你再次使用 Object.defineProperty() 方法并改变 writable 为 true。
- 性能考虑:虽然 Object.defineProperty() 提供了很大的灵活性,但是如果对大量对象或在性能敏感的场合使用它,需要注意其可能带来的性能影响。特别是动态的存取器(getter和setter),可能会因为每次访问属性都需要执行额外的代码而降低性能。
- 不可配置性:一旦将属性的 configurable 设置为 false,你将无法更改 enumerable、configurable 以及 writable 属性,也无法将该属性从对象上删除。此操作是不可逆的,因此需要谨慎使用。
- 如果在 set 里面直接为 obj 需要定义的属性赋值,会形成一个死循环,所以需要一个变量来代替 obj 的属性值进行存储和充当返回值
const obj = {};
Object.defineProperty(obj, 'name', {
get() {
return this.name;
},
set(value) {
this.name = value;
}
});
obj.name = 2;
Proxy
Proxy 是一个内置的构造函数,用于创建一个对象的代理(proxy),从而可以在基本操作(如属性查找、赋值、枚举、函数调用等)之前拦截并定义自定义行为。
const proxy = new Proxy(target, handler);
- target: 要使用代理包装的原始对象。
- handler: 一个通常以函数作为属性的对象,这些函数定义了在执行各种操作时代理的行为。 -handler 对象
handler 对象是由一系列“陷阱”(trap)组成的,这些陷阱是函数,当执行某些基本操作时会被调用。下面是一些常用的陷阱:
- get(target, prop, receiver): 拦截对象属性的读取操作。
- set(target, prop, value, receiver): 拦截对象属性的设置操作。
- has(target, prop): 拦截 prop in proxy 的操作,以及相关的 Reflect.has()。
- deleteProperty(target, prop): 拦截删除对象属性的操作。
- apply(target, thisArg, argumentsList): 拦截函数调用。
- construct(target, argumentsList, newTarget): 拦截 new 操作符。
const target = {
message: 'Hello, world!'
};
const handler = {
get(obj, prop){
if (prop in obj) {
return obj[prop];
}
return `Property ${prop} doesn't exist.`;
},
set(obj, prop, value) {
// 你可以添加额外的逻辑进行验证或转换等
if (typeof value !== 'string') {
throw new TypeError('Value must be a string.');
}
obj[prop] = value;
// 通常情况下返回 true 表示设置成功
return true;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message); // Hello, world!
console.log(proxy.nonExistent); // Property nonExistent doesn't exist.
proxy.message = 'Hello, Proxy!'; // 成功设置新的值
proxy.another = 123; // 抛出 TypeError: Value must be a string.
处理 Map 或者 Set
const m = new Map(); // const m = new Set()
const proxy = new Proxy(m, {
get(target, key){
// 如果是方法,修正 this 指向
let value = target[key];
if(value instanceof Function){
retrun value.bind(target);
}
return value;
},
set(target, key, value){
target[key] = value;
}
})
注意事项
- Proxy 是不透明的,在外部不能直接判断一个对象是否是 Proxy。
- 不是所有的对象操作都可以被拦截,例如修改对象的原型就不能被 Proxy 拦截。
- Proxy 对象具有一定性能影响,对于要求性能的场合应谨慎使用。
- Proxy 内部的一些操作(如 handler.apply 和 handler.construct)必须遵守特定的返回值规约,否则会抛出 TypeError。
- 一旦代理创建,target 对象将被封装在 Proxy 中,外部对 Proxy 对象的所有操作都会通过 handler 对象来进行。
总之,Proxy 是一个功能强大的特性,可以帮助开发者设计出非常灵活且可定制的行为,用于各种高级抽象、API 包装器、观察者模式、虚拟对象等场景。