屬性的可列舉性和所有權

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.keys
Object.values
Object.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;
  },
};

另請參閱