属性的可枚举性和所有权

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;
  },
};

另请参阅