您现在的位置是:网站首页 > 属性描述符文章详情
属性描述符
陈川
【
JavaScript
】
50263人已围观
8757字
属性描述符的基本概念
属性描述符是JavaScript中用于定义或修改对象属性特性的对象。每个对象的属性都关联着一个属性描述符,它决定了这个属性如何工作。属性描述符分为两种类型:数据描述符和存取描述符。
数据描述符包含以下可选键值:
value
:属性的值writable
:是否可写enumerable
:是否可枚举configurable
:是否可配置
存取描述符包含以下可选键值:
get
:获取属性值的函数set
:设置属性值的函数enumerable
:是否可枚举configurable
:是否可配置
// 数据描述符示例
const obj1 = {};
Object.defineProperty(obj1, 'name', {
value: 'John',
writable: true,
enumerable: true,
configurable: true
});
// 存取描述符示例
const obj2 = {
_age: 30
};
Object.defineProperty(obj2, 'age', {
get() {
return this._age;
},
set(newValue) {
this._age = newValue;
},
enumerable: true,
configurable: true
});
获取属性描述符
可以使用Object.getOwnPropertyDescriptor()
方法获取对象某个属性的描述符。如果要获取对象所有自身属性的描述符,可以使用Object.getOwnPropertyDescriptors()
。
const person = {
name: 'Alice',
age: 25
};
// 获取单个属性的描述符
const nameDescriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(nameDescriptor);
/*
{
value: 'Alice',
writable: true,
enumerable: true,
configurable: true
}
*/
// 获取所有属性的描述符
const allDescriptors = Object.getOwnPropertyDescriptors(person);
console.log(allDescriptors);
/*
{
name: {
value: 'Alice',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 25,
writable: true,
enumerable: true,
configurable: true
}
}
*/
数据描述符详解
数据描述符是最常用的属性描述符类型,它直接定义了属性的值和特性。
value属性
value
指定了属性的初始值,可以是任何有效的JavaScript值。
const car = {};
Object.defineProperty(car, 'brand', {
value: 'Toyota'
});
console.log(car.brand); // "Toyota"
writable属性
writable
决定属性值是否可以被修改。默认为false
。
const book = {};
Object.defineProperty(book, 'title', {
value: 'JavaScript Guide',
writable: false
});
book.title = 'New Title'; // 静默失败,严格模式下会抛出TypeError
console.log(book.title); // "JavaScript Guide"
enumerable属性
enumerable
决定属性是否会在for...in
循环或Object.keys()
中显示。默认为false
。
const colors = {
red: '#FF0000',
green: '#00FF00'
};
Object.defineProperty(colors, 'blue', {
value: '#0000FF',
enumerable: false
});
console.log(Object.keys(colors)); // ["red", "green"]
for (const color in colors) {
console.log(color); // 只输出"red"和"green"
}
configurable属性
configurable
决定属性是否可以被删除或特性是否可以被修改。默认为false
。
const config = {};
Object.defineProperty(config, 'apiUrl', {
value: 'https://api.example.com',
configurable: false
});
// 尝试删除
delete config.apiUrl; // 静默失败
console.log(config.apiUrl); // "https://api.example.com"
// 尝试修改特性
Object.defineProperty(config, 'apiUrl', {
writable: true
}); // 抛出TypeError
存取描述符详解
存取描述符通过getter和setter函数来控制属性的访问和修改。
getter函数
get
是一个无参函数,当访问属性时会被调用。
const circle = {
_radius: 5
};
Object.defineProperty(circle, 'diameter', {
get() {
return this._radius * 2;
}
});
console.log(circle.diameter); // 10
setter函数
set
是一个接收一个参数的函数,当设置属性时会被调用。
const circle = {
_radius: 5
};
Object.defineProperty(circle, 'diameter', {
get() {
return this._radius * 2;
},
set(value) {
this._radius = value / 2;
}
});
circle.diameter = 14;
console.log(circle._radius); // 7
使用Object.defineProperties定义多个属性
可以一次性定义多个属性及其描述符。
const employee = {};
Object.defineProperties(employee, {
firstName: {
value: 'John',
writable: true
},
lastName: {
value: 'Doe',
writable: true
},
fullName: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
const parts = name.split(' ');
this.firstName = parts[0] || '';
this.lastName = parts[1] || '';
}
}
});
console.log(employee.fullName); // "John Doe"
employee.fullName = 'Jane Smith';
console.log(employee.firstName); // "Jane"
console.log(employee.lastName); // "Smith"
属性描述符的继承与原型链
属性描述符不会被继承,但通过原型链访问属性时,描述符的特性仍然会影响属性的行为。
function Person(name) {
this.name = name;
}
Person.prototype = {
get greeting() {
return `Hello, my name is ${this.name}`;
}
};
const person = new Person('Alice');
console.log(person.greeting); // "Hello, my name is Alice"
// 获取原型上属性的描述符
const greetingDescriptor = Object.getOwnPropertyDescriptor(
Person.prototype,
'greeting'
);
console.log(greetingDescriptor.enumerable); // false
密封、冻结和防止扩展
属性描述符与对象的不可变性操作密切相关。
Object.preventExtensions
阻止对象添加新属性,但现有属性仍可修改。
const obj = { prop: 'value' };
Object.preventExtensions(obj);
obj.newProp = 'new'; // 静默失败,严格模式下抛出TypeError
console.log(obj.newProp); // undefined
Object.seal
密封对象,阻止添加新属性并将所有现有属性标记为configurable: false
。
const obj = { prop: 'value' };
Object.seal(obj);
delete obj.prop; // 静默失败
obj.prop = 'new value'; // 允许修改
console.log(obj.prop); // "new value"
Object.freeze
冻结对象,阻止添加新属性、删除现有属性或修改现有属性(所有属性变为writable: false
)。
const obj = { prop: 'value' };
Object.freeze(obj);
obj.prop = 'new value'; // 静默失败
console.log(obj.prop); // "value"
实际应用场景
数据验证
使用setter进行数据验证。
function createUser(name, age) {
const user = {};
Object.defineProperties(user, {
name: {
value: name,
writable: true
},
age: {
get() {
return this._age;
},
set(value) {
if (typeof value !== 'number' || value < 0 || value > 120) {
throw new Error('Invalid age');
}
this._age = value;
}
}
});
user.age = age;
return user;
}
const user = createUser('Bob', 30);
user.age = 25; // 正常
user.age = 150; // 抛出错误
计算属性
使用getter创建计算属性。
const rectangle = {
width: 10,
height: 5,
get area() {
return this.width * this.height;
}
};
console.log(rectangle.area); // 50
rectangle.width = 20;
console.log(rectangle.area); // 100
私有属性模拟
结合闭包和属性描述符模拟私有属性。
function createCounter() {
let _count = 0;
const counter = {};
Object.defineProperties(counter, {
value: {
get() {
return _count;
}
},
increment: {
value() {
_count++;
}
},
decrement: {
value() {
_count--;
}
}
});
return counter;
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.value); // 2
counter._count = 100; // 无效
console.log(counter.value); // 2
与ES6类的结合使用
属性描述符可以与ES6类一起使用,实现更精细的属性控制。
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
get fahrenheit() {
return this.celsius * 1.8 + 32;
}
set fahrenheit(value) {
this.celsius = (value - 32) / 1.8;
}
static fromFahrenheit(value) {
return new Temperature((value - 32) / 1.8);
}
}
const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 86;
console.log(temp.celsius); // 30
// 获取类中属性的描述符
const descriptor = Object.getOwnPropertyDescriptor(
Temperature.prototype,
'fahrenheit'
);
console.log(descriptor.get); // [Function: get fahrenheit]
性能考虑
使用属性描述符可能会对性能产生轻微影响,特别是在频繁访问的属性上使用getter/setter时。在性能关键的代码中应谨慎使用。
// 简单属性访问
const obj1 = { value: 0 };
console.time('simple');
for (let i = 0; i < 1000000; i++) {
obj1.value += 1;
}
console.timeEnd('simple');
// 使用getter/setter
const obj2 = {
_value: 0,
get value() {
return this._value;
},
set value(v) {
this._value = v;
}
};
console.time('getter-setter');
for (let i = 0; i < 1000000; i++) {
obj2.value += 1;
}
console.timeEnd('getter-setter');
浏览器兼容性
属性描述符在现代浏览器中得到了广泛支持,包括:
- Chrome 5+
- Firefox 4+
- Safari 5+
- Opera 12+
- Edge 12+
- IE 9+
对于旧版浏览器,可以使用polyfill或回退方案。
与其他特性的交互
属性描述符与JavaScript的其他特性如Proxy、Reflect等有良好的交互。
// 结合Proxy使用
const target = {};
const proxy = new Proxy(target, {
defineProperty(t, prop, descriptor) {
console.log(`Defining property ${prop}`);
return Reflect.defineProperty(t, prop, descriptor);
}
});
Object.defineProperty(proxy, 'newProp', {
value: 'proxy value'
});
// 控制台输出: "Defining property newProp"
console.log(target.newProp); // "proxy value"
常见陷阱与注意事项
- 默认值:使用
Object.defineProperty
时,所有描述符特性默认为false
,而普通对象字面量定义的属性默认为true
。
const obj1 = { prop: 'value' };
const descriptor1 = Object.getOwnPropertyDescriptor(obj1, 'prop');
console.log(descriptor1.writable); // true
const obj2 = {};
Object.defineProperty(obj2, 'prop', { value: 'value' });
const descriptor2 = Object.getOwnPropertyDescriptor(obj2, 'prop');
console.log(descriptor2.writable); // false
-
不可逆操作:一旦将属性设置为
configurable: false
,就无法再将其改回true
。 -
静默失败:在非严格模式下,违反属性描述符规则的操作通常会静默失败,不会抛出错误。
-
原型链上的属性:
Object.defineProperty
只能定义对象自身的属性,不能定义原型链上的属性。