对象属性配置
属性标志和属性描述符
属性标志
对象属性(properties),除value
外还有三个特殊的特性(attributes),也就是所谓的‘标志’。
writable
- 如果为true
,则值可以被修改,否则它是只可读的。enumerable
- 如果为true
,则会呗在循环中列出,否则不会被列出。configurable
- 如果为true
,则此属性可以被删除。
获取这些标志使用:
Object.getOwnPropertyDescriptor(obj, propertyName);
- obj:需要查找的对象
- propertyName:需要查找的属性
const obj = {
a: 1,
};
Object.getOwnPropertyDescriptor(obj, "a");
// {
// value: 1,
// writable: true,
// enumerable: true,
// configurable: true
// }
修改属性标志
Object.defineProperty(obj, propertyName, descriptor);
- obj,propertyName:需要修改的对象和属性
- descriptor:需要修改的属性标志
如果没有提供任何标志,则会假定它是false
。
let user = {};
Object.defineProperty(user, "name", {
value: "John",
});
let descriptor = Object.getOwnPropertyDescriptor(user, "name");
alert(JSON.stringify(descriptor, null, 2));
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
只读
通过修改writable
标志来把user.name
设置为只读。
let user = {
name: "John",
};
Object.defineProperty(user, "name", {
writable: false,
});
user.name = "Pete"; // Error: Cannot assign to read only property 'name'
只在严格模式下才会出现 Errors
非严格模式下,在对不可写的属性等进行写入操作时,不回出现错误。但是操作不会成功。
let user = {};
Object.defineProperty(user, "name", {
value: "John",
// 对于新属性,我们需要明确地列出哪些是 true
enumerable: true,
configurable: true,
});
alert(user.name); // John
user.name = "Pete"; // Error
不可枚举
let user = {
name: "John",
toString() {
return this.name;
},
};
// 默认情况下,我们的两个属性都会被列出:
for (let key in user) alert(key); // name, toString
将enumerable:false
。这样就不会出现在for...in
循环中了。
let user = {
name: "John",
toString() {
return this.name;
},
};
Object.defineProperty(user, "toString", {
enumerable: false,
});
// 现在我们的 toString 消失了:
for (let key in user) alert(key); // name
不可枚举的属性也会被Object.keys
排除。
alert(Object.keys(user)); // name
不可配置
configurable:false
let descriptor = Object.getOwnPropertyDescriptor(Math, "PI");
alert(JSON.stringify(descriptor, null, 2));
/*
{
"value": 3.141592653589793,
"writable": false,
"enumerable": false,
"configurable": false
}
*/
因此开发人员无法修改Math.PI
的值或者覆盖它。
Math.PI = 3; // Error,因为其 writable: false
// 删除 Math.PI 也不会起作用
// 也无法对其进行更改
Object.defineProperty(Math, "PI", { writable: true });
configurable:false
是防止更改和删除属性标志,但是允许更改对象的值。
let user = {
name: "John",
};
Object.defineProperty(user, "name", {
configurable: false,
});
user.name = "Pete"; // 正常工作
delete user.name; // Error
将user.name
设置为一个永不可改的常量,就想内建的Math.PI
let user = {
name: "John",
};
Object.defineProperty(user, "name", {
writable: false,
configurable: false,
});
// 不能修改user.name 或它的标志
// 下面的操作都不起作用
user.name = "Pete";
delete user.name;
Object.defineProperty(user, "name", { value: "Pete" });
唯一可行的特性更改:writable true -> false
对于不可配置的属性,我们可以将writable:true
改为false
,从而防止其值被修改(以添加另一层保护)。但无法反向操作。
let user = {
name: "John",
};
Object.defineProperty(user, "name", {
writable: true,
configurable: false,
});
// 不能修改 user.name 或它的标志
// 下面的所有操作都不起作用:
Object.defineProperty(user, "name", {
writable: false,
configurable: false,
});
Object.defineProperties
一次设置多条属性
Object.defineProperties(obj, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
});
Object.getOwnPropertyDescriptors
一次获取所有属性描述符。
Object.getOwnPropertyDescriptors(obj);
let user = {
name: "John",
age:18
};
Object.defineProperty(user, "name", {
writable: false,
configurable: false
});
let clone = Object.getOwnPropertyDescriptors(user);
console.log(clone);
// {
// "name": {
// "value": "John",
// "writable": false,
// "enumerable": true,
// "configurable": false
// },
// "age": {
// "value": 18,
// "writable": true,
// "enumerable": true,
// "configurable": true
// }
// }
我们可以将它用在克隆上,for...in
循环和Object.assign
只会复制数据,而不会复制描述符。另外for...in
循环会忽略symbol类型和不可枚举的属性。
let user = {
name: "John",
age:18
};
Object.defineProperty(user, "name", {
writable: false,
configurable: false
});
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(user));
其他属性
- Object.preventExtensions(obj)
禁止向对象添加新属性
- Object.seal(obj)
禁止添加/删除属性。为所有现有的属性设置configurable:false
。
- Object.freeze(obj)
禁止添加/删除/更爱属性。为所有现有的属性设置writable:false
和configurable:false
。
- Object.isExtensible(obj)
如果添加属性被禁止,则返回false
,否则返回true
。
- Object.isSealed(obj)
如果添加/删除属性被禁止,并且所有现有的属性都具有 configurable: false
则返回 true
。
- Object.isFrozen(obj)
如果添加/删除/更改属性被禁止,并且所有当前属性都是 configurable: false
, writable: false
,则返回 true。
属性的getter和setter
有两种类型的对象属性
- 数据属性
- 访问器属性(accessor property)。它们本质是用来获取和设置值的函数,但从外部代码来看就像是常规属性。
getter和setter
let obj = {
get propName(){
// 读取操作
},
set propName(value)[
// 写入操作
]
}
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
}
set fullName(value){
[this.name, this.surname] = value.split(" ");
}
};
console.log(user.fullName); // John Smith
// set fullName 将以给定值执行
user.fullName = "Alice Cooper";
console.log(user.fullName);// Alice Cooper
访问器描述符
与数据属性的不同
对于访问器属性,没有value
和writable
,但是有get
和set
函数。
所以访问器描述符可能有
get
-- 一个没有参数的函数,在读取属性时工作。set
-- 带有一个参数的函数,当属性被设置时调用。enumerable
-- 与数据属性的相同configurable
-- 与数据属性的相同
let user = {
name: "John",
surname: "Smith"
};
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
alert(user.fullName); // John Smith
for(let key in user) alert(key); // name, surname
对属性进行控制
let user = {
get name(){
return this._name;
},
set name(value){
if(value.length<4){
console.log('赋值太短了')
return
}
this._name = value;
}
}
user.name = "Pete";
console.log(user.name); // Pete
user.name = '';//赋值太短了
兼容性
function User(name, age) {
this.name = name;
this.age = age;
}
let john = new User("John", 25);
alert( john.age ); // 25
// 有天我们突然想存储一个精确的日期但是,这时候 又会影响之前的代码
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}
let john = new User("John", new Date(1992, 6, 1));
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// 年龄是根据当前日期和生日计算得出的
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1992, 6, 1));
alert( john.birthday ); // birthday 是可访问的
alert( john.age ); // ……age 也是可访问的
这样我们就可以既保留age又可以保留birthday了