属性描述符
属性描述符
JavaScript中的对象属性不仅仅是简单的键值对,每个属性实际上都关联着一个属性描述符对象。属性描述符定义了属性的行为特征,包括是否可写、可枚举、可配置等。理解属性描述符是深入掌握JavaScript对象模型的关键。
属性描述符的类型
属性描述符分为两种主要类型:数据描述符和存取描述符。数据描述符直接定义属性的值及其特性,而存取描述符则通过getter和setter函数控制属性的访问。
数据描述符包含以下可选键:
value
:属性的值writable
:是否可修改enumerable
:是否可枚举configurable
:是否可配置
存取描述符包含:
get
:获取属性值的函数set
:设置属性值的函数enumerable
:是否可枚举configurable
:是否可配置
// 数据描述符示例
const obj1 = {};
Object.defineProperty(obj1, 'name', {
value: 'John',
writable: true,
enumerable: true,
configurable: true
});
// 存取描述符示例
const obj2 = {};
let _age = 25;
Object.defineProperty(obj2, 'age', {
get() { return _age; },
set(newVal) { _age = newVal > 0 ? newVal : _age; },
enumerable: true,
configurable: false
});
获取属性描述符
可以使用Object.getOwnPropertyDescriptor()
方法获取对象某个属性的描述符:
const person = { name: 'Alice' };
const descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor);
/*
{
value: "Alice",
writable: true,
enumerable: true,
configurable: true
}
*/
要获取对象所有自身属性的描述符,可以使用Object.getOwnPropertyDescriptors()
:
const car = {
brand: 'Toyota',
year: 2020
};
const descriptors = Object.getOwnPropertyDescriptors(car);
console.log(descriptors);
/*
{
brand: {
value: "Toyota",
writable: true,
enumerable: true,
configurable: true
},
year: {
value: 2020,
writable: true,
enumerable: true,
configurable: true
}
}
*/
属性特性详解
writable特性
当writable
为false
时,属性值不能被重新赋值。在严格模式下尝试修改会抛出错误:
const obj = {};
Object.defineProperty(obj, 'readOnly', {
value: 'initial',
writable: false
});
console.log(obj.readOnly); // "initial"
obj.readOnly = 'new value'; // 非严格模式下静默失败
console.log(obj.readOnly); // 仍然是"initial"
'use strict';
obj.readOnly = 'new value'; // TypeError: Cannot assign to read only property
enumerable特性
enumerable
控制属性是否出现在for...in
循环和Object.keys()
中:
const obj = {};
Object.defineProperties(obj, {
visible: { value: 1, enumerable: true },
hidden: { value: 2, enumerable: false }
});
console.log(Object.keys(obj)); // ["visible"]
for (let key in obj) {
console.log(key); // 只输出"visible"
}
configurable特性
configurable
为false
时,不能删除属性,也不能将其改为存取器属性,且不能修改除writable
和value
外的特性:
const obj = {};
Object.defineProperty(obj, 'x', {
value: 1,
configurable: false
});
// 尝试删除
delete obj.x; // 静默失败
console.log(obj.x); // 1
// 尝试修改特性
Object.defineProperty(obj, 'x', {
enumerable: false
}); // TypeError: Cannot redefine property: x
// 唯一允许的修改
Object.defineProperty(obj, 'x', {
writable: false
}); // 成功
使用场景
创建不可变属性
function createImmutableObject(properties) {
const obj = {};
for (const [key, value] of Object.entries(properties)) {
Object.defineProperty(obj, key, {
value,
writable: false,
configurable: false
});
}
return obj;
}
const constants = createImmutableObject({
PI: 3.14159,
E: 2.71828
});
实现私有属性模式
function createPrivateProperties(obj, privateData) {
const privateStore = new WeakMap();
privateStore.set(obj, privateData);
for (const key in privateData) {
Object.defineProperty(obj, key, {
get() {
return privateStore.get(this)[key];
},
set(value) {
privateStore.get(this)[key] = value;
},
enumerable: true
});
}
return obj;
}
const user = {};
createPrivateProperties(user, {
_password: 'secret123',
_token: 'abc123'
});
console.log(user._password); // "secret123"
user._password = 'newSecret';
console.log(user._password); // "newSecret"
原型链与属性描述符
属性描述符在原型链中表现出特殊行为。当通过原型链继承的属性被访问时,其描述符特性会影响属性的访问方式:
const proto = {
inheritedProp: 'proto value'
};
Object.defineProperty(proto, 'readOnlyProp', {
value: 'cannot change',
writable: false
});
const child = Object.create(proto);
child.ownProp = 'child value';
console.log(child.inheritedProp); // "proto value"
child.inheritedProp = 'new value'; // 实际上会在child上创建新属性
console.log(child.inheritedProp); // "new value"
console.log(proto.inheritedProp); // 仍然是"proto value"
child.readOnlyProp = 'attempt change'; // 静默失败
console.log(child.readOnlyProp); // "cannot change"
批量定义属性
Object.defineProperties()
允许一次性定义多个属性:
const config = {};
Object.defineProperties(config, {
apiUrl: {
value: 'https://api.example.com',
writable: false,
enumerable: true
},
maxRetries: {
value: 3,
writable: true
},
logLevel: {
get() {
return this._logLevel || 'info';
},
set(value) {
if (['debug', 'info', 'warn', 'error'].includes(value)) {
this._logLevel = value;
}
},
enumerable: true
}
});
与类语法结合
在ES6类中,可以使用访问器属性和静态方法配合属性描述符:
class Temperature {
constructor(celsius) {
this._celsius = celsius;
}
get fahrenheit() {
return this._celsius * 9/5 + 32;
}
set fahrenheit(value) {
this._celsius = (value - 32) * 5/9;
}
static createReadOnly(name, value) {
const temp = new Temperature(0);
Object.defineProperty(temp, name, {
value,
writable: false,
configurable: false
});
return temp;
}
}
const temp = Temperature.createReadOnly('unit', 'Celsius');
console.log(temp.unit); // "Celsius"
temp.unit = 'Fahrenheit'; // 静默失败
属性描述符的限制
某些操作会受到属性描述符的限制:
- 不能将不可配置的属性从数据属性改为存取器属性,反之亦然
- 不能修改不可配置属性的
enumerable
特性 - 对于数据属性,如果
writable
为false
且configurable
为false
,则不能修改value
- 如果访问器属性的
configurable
为false
,则不能修改get
和set
函数
const obj = {};
Object.defineProperty(obj, 'x', {
value: 1,
writable: false,
configurable: false
});
// 以下操作都会抛出TypeError
Object.defineProperty(obj, 'x', {
get() { return 2; }
});
Object.defineProperty(obj, 'x', {
enumerable: false
});
Object.defineProperty(obj, 'x', {
value: 2
});
与Proxy结合
属性描述符可以与Proxy结合,实现更高级的属性控制:
const handler = {
defineProperty(target, prop, descriptor) {
if (prop.startsWith('_')) {
throw new Error(`Cannot define private property "${prop}"`);
}
// 强制所有属性不可枚举
return Reflect.defineProperty(target, prop, {
...descriptor,
enumerable: false
});
}
};
const target = {};
const proxy = new Proxy(target, handler);
proxy.publicProp = 'value'; // 成功
console.log(Object.keys(proxy)); // []
try {
proxy._privateProp = 'secret'; // Error: Cannot define private property "_privateProp"
} catch (e) {
console.error(e.message);
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn