深入了解對象屬性標(biāo)志以及描述符
屬性標(biāo)志以及描述符
正如我們所知,對象可以存儲(chǔ)屬性。
到目前為止,屬性對我們來說只是一個(gè)簡單的“鍵-值”對。但對象屬性實(shí)際上是一個(gè)更靈活和強(qiáng)大的東西。
本章我們將學(xué)習(xí)額外的配置選項(xiàng),下一章我們將看到如何無形地將它們轉(zhuǎn)換為getter/setter函數(shù)。
屬性標(biāo)志
652/5000 對象屬性除了值之外,還有三個(gè)特殊的屬性(所謂的“標(biāo)志”):
writable—如果為true,該值可以修改,否則為只讀。enumerable——如果為true,則在循環(huán)中列出,否則不列出。configurable-如果為true,屬性可以被刪除,這些屬性可以被修改,否則不。
我們還沒看到他們,因?yàn)樗麄兺ǔ2粫?huì)出現(xiàn)。當(dāng)我們以“通常的方式”創(chuàng)建一個(gè)屬性時(shí),它們都是正確的。但我們也可以隨時(shí)改變它們。
首先,讓我們看看如何獲得這些標(biāo)志。
Object.getOwnPropertyDescriptor允許查詢關(guān)于屬性的完整信息。
語法是:
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
obj:
要從中獲取信息的對象。
propertyName:
屬性的名稱。
返回值是一個(gè)所謂的“屬性描述符”對象:它包含值和所有標(biāo)記。
例如:
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
要更改標(biāo)記,我們可以使用Object.defineProperty。
語法是:
Object.defineProperty(obj, propertyName, descriptor)
obj, propertyName
用于應(yīng)用描述符的對象及其屬性。
descriptor
要應(yīng)用的屬性描述符對象。
如果該屬性存在,defineProperty將更新其標(biāo)記。否則,它將創(chuàng)建具有給定值和標(biāo)志的屬性;在這種情況下,如果沒有提供標(biāo)志,則假定它為假。
例如,這里創(chuàng)建了一個(gè)帶有所有假標(biāo)志的屬性名:
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
}
*/
與上面的“正常創(chuàng)建的”user.name比較一下:現(xiàn)在所有的標(biāo)志都是假的。如果這不是我們想要的那么我們最好在descriptor中將它們設(shè)為真。
現(xiàn)在讓我們通過例子來看看標(biāo)記的效果。
不可寫
讓我們通過改變writable標(biāo)志使user.name不可寫(不能被重新分配):
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false
});
user.name = "Pete"; // Error: Cannot assign to read only property 'name'
現(xiàn)在沒有人可以更改我們的用戶名,除非他們應(yīng)用自己的defineProperty來覆蓋我們的用戶名。
下面是相同的例子,但屬性是從頭創(chuàng)建的:
let user = { };
Object.defineProperty(user, "name", {
value: "John",
// for new properties we need to explicitly list what's true
enumerable: true,
configurable: true
});
alert(user.name); // John
user.name = "Pete"; // Error
不可枚舉
現(xiàn)在讓我們?yōu)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">user添加一個(gè)自定義toString。
通常,對象的內(nèi)置toString是不可枚舉的,它不會(huì)出現(xiàn)在for..in。但如果我們添加自己的toString,那么默認(rèn)情況下它會(huì)出現(xiàn)在for..in,是這樣的:
let user = {
name: "John",
toString() {
return this.name;
}
};
// By default, both our properties are listed:
for (let key in user) alert(key); // name, toString
如果我們不喜歡它,那么可以設(shè)置enumerable:false。那么它就不會(huì)出現(xiàn)在for..in循環(huán),就像內(nèi)置的一樣:
let user = {
name: "John",
toString() {
return this.name;
}
};
Object.defineProperty(user, "toString", {
enumerable: false
});
// Now our toString disappears:
for (let key in user) alert(key); // name
不可枚舉屬性也被排除在Object.keys之外:
alert(Object.keys(user)); // name
不可配置
不可配置的標(biāo)志(configurable:fals)有時(shí)是內(nèi)置對象和屬性的預(yù)置標(biāo)志。
不能刪除不可配置的屬性。
例如,Math.PI是不可寫、不可枚舉和不可配置的:
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
// delete Math.PI won't work either
使屬性不可配置是一條單行道。我們不能用defineProperty把它改回來。
確切地說,不可配置性在defineProperty上強(qiáng)加了幾個(gè)限制:
無法更改可配置標(biāo)志。
不能改變enumerable標(biāo)志。
不能將
writable: false改為true(反過來也可以)。不能更改訪問器屬性的
get/set(但如果沒有,可以分配它們)。
“configurable:false”的思想是為了防止屬性標(biāo)記的更改和刪除,同時(shí)允許更改其值。
這里user.name是不可配置的,但是我們?nèi)匀豢梢孕薷乃?因?yàn)樗强蓪懙?:
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
configurable: false
});
user.name = "Pete"; // works fine
delete user.name; // Error
這里我們將user.name設(shè)為“永久密封”常數(shù):
let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false,
configurable: false
});
// won't be able to change user.name or its flags
// all this won't work:
user.name = "Pete";
delete user.name;
Object.defineProperty(user, "name", { value: "Pete" });
Object.defineProperties
有一個(gè)方法Object.defineProperties(obj, descriptors)允許一次定義許多屬性。
語法是:
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
例如:
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});
Object.getOwnPropertyDescriptors
要一次性獲得所有屬性描述符,我們可以使用Object.getOwnPropertyDescriptors(obj)方法。
和object.defineproperties一起,它可以作為一種“識(shí)別標(biāo)志”的方式來克隆對象:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
通常,當(dāng)我們克隆一個(gè)對象時(shí),我們使用賦值來復(fù)制屬性,像這樣:
for (let key in user) {
clone[key] = user[key]
}
但這不會(huì)復(fù)制標(biāo)記。因此,如果我們想要一個(gè)“更好的”克隆,那么Object.defineProperties是首選。
另一個(gè)區(qū)別是for…in會(huì)忽略符號(hào)屬性,但Object.getOwnPropertyDescriptors返回所有屬性描述符,包括符號(hào)描述符。
全局密封對象
屬性描述符在單個(gè)屬性的級(jí)別上工作。
還有一些方法可以限制對整個(gè)對象的訪問:
Object.preventExtensions(obj)
禁止向?qū)ο筇砑有聦傩浴?/p>
Object.seal(obj)
禁止添加/刪除屬性。設(shè)置configurable: false為所有現(xiàn)有的屬性。
Object.freeze(obj)
禁止添加/刪除/更改屬性。設(shè)置configurable: false,writable: false所有現(xiàn)有的屬性。
對他們也有一些測試:
Object.isExtensible(obj)
如果禁止添加屬性,則返回false,否則返回true。
Object.isSealed(obj)
如果禁止添加/刪除屬性,則返回true, 并且所有現(xiàn)有屬性都是configurable: false。
Object.isFrozen(obj)
如果禁止添加/刪除/更改屬性,則返回true,并且當(dāng)前所有屬性都是configurable: false, writable: false。
