屬性的可列舉性和所有權
JavaScript 物件中的每個屬性都可以透過三個因素進行分類
- 可列舉或不可列舉;
- 字串或符號;
- 自身屬性或從原型鏈繼承的屬性。
可列舉屬性是指其內部可列舉標誌設定為 true 的屬性,這是透過簡單賦值或透過屬性初始化程式建立的屬性的預設值。透過Object.defineProperty等定義的屬性預設情況下不可列舉。大多數迭代方式(如for...in迴圈和Object.keys)僅訪問可列舉鍵。
屬性的所有權取決於屬性是否直接屬於物件,而不是屬於其原型鏈。
所有屬性,無論是否可列舉、是字串還是符號、是自身屬性還是繼承屬性,都可以使用點表示法或括號表示法進行訪問。在本節中,我們將重點介紹 JavaScript 提供的逐個訪問物件屬性組的方法。
查詢物件屬性
有四種內建方法可以查詢物件的屬性。它們都支援字串和符號鍵。下表總結了每種方法何時返回true。
| 可列舉,自身 | 可列舉,繼承 | 不可列舉,自身 | 不可列舉,繼承 | |
|---|---|---|---|---|
propertyIsEnumerable() |
true ✅ |
false ❌ |
false ❌ |
false ❌ |
hasOwnProperty() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
Object.hasOwn() |
true ✅ |
false ❌ |
true ✅ |
false ❌ |
in |
true ✅ |
true ✅ |
true ✅ |
true ✅ |
遍歷物件屬性
JavaScript 中有許多方法可以遍歷物件的屬性組。有時,這些屬性作為陣列返回;有時,它們在迴圈中逐個迭代;有時,它們用於構造或修改另一個物件。下表總結了何時可能會訪問屬性。
僅訪問字串屬性或僅訪問符號屬性的方法將有一個額外的註釋。✅表示將訪問此型別的屬性;❌表示不會訪問。
| 可列舉,自身 | 可列舉,繼承 | 不可列舉,自身 | 不可列舉,繼承 | |
|---|---|---|---|---|
Object.keysObject.valuesObject.entries |
✅ (字串) |
❌ | ❌ | ❌ |
Object.getOwnPropertyNames |
✅ (字串) |
❌ | ✅ (字串) |
❌ |
Object.getOwnPropertySymbols |
✅ (符號) |
❌ | ✅ (符號) |
❌ |
Object.getOwnPropertyDescriptors |
✅ | ❌ | ✅ | ❌ |
Reflect.ownKeys |
✅ | ❌ | ✅ | ❌ |
for...in |
✅ (字串) |
✅ (字串) |
❌ | ❌ |
Object.assign(第一個引數之後) |
✅ | ❌ | ❌ | ❌ |
| 物件展開 | ✅ | ❌ | ❌ | ❌ |
根據可列舉性/所有權獲取屬性
請注意,這並非適用於所有情況的最有效演算法,但對於快速演示很有用。
- 檢測可以透過
SimplePropertyRetriever.theGetMethodYouWant(obj).includes(prop)進行。 - 迭代可以透過
SimplePropertyRetriever.theGetMethodYouWant(obj).forEach((value, prop) => {});進行(或使用filter()、map()等)。
js
const SimplePropertyRetriever = {
getOwnEnumerables(obj) {
return this._getPropertyNames(obj, true, false, this._enumerable);
// Or could use for...in filtered with Object.hasOwn or just this: return Object.keys(obj);
},
getOwnNonenumerables(obj) {
return this._getPropertyNames(obj, true, false, this._notEnumerable);
},
getOwnEnumerablesAndNonenumerables(obj) {
return this._getPropertyNames(
obj,
true,
false,
this._enumerableAndNotEnumerable,
);
// Or just use: return Object.getOwnPropertyNames(obj);
},
getPrototypeEnumerables(obj) {
return this._getPropertyNames(obj, false, true, this._enumerable);
},
getPrototypeNonenumerables(obj) {
return this._getPropertyNames(obj, false, true, this._notEnumerable);
},
getPrototypeEnumerablesAndNonenumerables(obj) {
return this._getPropertyNames(
obj,
false,
true,
this._enumerableAndNotEnumerable,
);
},
getOwnAndPrototypeEnumerables(obj) {
return this._getPropertyNames(obj, true, true, this._enumerable);
// Or could use unfiltered for...in
},
getOwnAndPrototypeNonenumerables(obj) {
return this._getPropertyNames(obj, true, true, this._notEnumerable);
},
getOwnAndPrototypeEnumerablesAndNonenumerables(obj) {
return this._getPropertyNames(
obj,
true,
true,
this._enumerableAndNotEnumerable,
);
},
// Private static property checker callbacks
_enumerable(obj, prop) {
return Object.prototype.propertyIsEnumerable.call(obj, prop);
},
_notEnumerable(obj, prop) {
return !Object.prototype.propertyIsEnumerable.call(obj, prop);
},
_enumerableAndNotEnumerable(obj, prop) {
return true;
},
// Inspired by http://stackoverflow.com/a/8024294/271577
_getPropertyNames(obj, iterateSelf, iteratePrototype, shouldInclude) {
const props = [];
do {
if (iterateSelf) {
Object.getOwnPropertyNames(obj).forEach((prop) => {
if (props.indexOf(prop) === -1 && shouldInclude(obj, prop)) {
props.push(prop);
}
});
}
if (!iteratePrototype) {
break;
}
iterateSelf = true;
obj = Object.getPrototypeOf(obj);
} while (obj);
return props;
},
};