可枚举性和属性所有权

JavaScript 对象中的每个属性都可以根据三个因素进行分类:

¥Every property in JavaScript objects can be classified by three factors:

  • 可枚举或不可枚举;
  • 字符串或 symbol
  • 自己的属性或从原型链继承的属性。

可枚举属性是那些内部可枚举标志设置为 true 的属性,这是通过简单赋值或属性初始值设定项创建的属性的默认值。默认情况下,通过 Object.defineProperty 定义的属性是不可枚举的。大多数迭代方式(例如 for...in 循环和 Object.keys)仅访问可枚举键。

¥Enumerable properties are those properties whose internal enumerable flag is set to true, which is the default for properties created via simple assignment or via a property initializer. Properties defined via Object.defineProperty and such are not enumerable by default. Most iteration means (such as for...in loops and Object.keys) only visit enumerable keys.

属性的所有权取决于属性是否直接属于对象而不是其原型链。

¥Ownership of properties is determined by whether the property belongs to the object directly and not to its prototype chain.

所有属性,无论是否可枚举,字符串或符号,自己的或继承的,都可以使用 点符号或括号符号 进行访问。在本节中,我们将重点讨论 JavaScript 提供的逐一访问一组对象属性的方法。

¥All properties, enumerable or not, string or symbol, own or inherited, can be accessed with dot notation or bracket notation. In this section, we will focus on the means provided by JavaScript to visit a group of object properties one-by-one.

查询对象属性

¥Querying object properties

有四种内置方法可以查询对象的属性。它们都支持字符串和符号键。下表总结了每种方法何时返回 true

¥There are four built-in ways to query a property of an object. They all support both string and symbol keys. The following table summarizes when each method returns true.

可枚举,拥有 可枚举、继承 不可枚举,拥有 不可枚举,继承
propertyIsEnumerable() true ✅ false ❌ false ❌ false ❌
hasOwnProperty() true ✅ false ❌ true ✅ false ❌
Object.hasOwn() true ✅ false ❌ true ✅ false ❌
in true ✅ true ✅ true ✅ true ✅

遍历对象属性

¥Traversing object properties

JavaScript 中有许多方法可以遍历对象的一组属性。有时,这些属性以数组形式返回;有时,它们会在循环中逐一迭代;有时,它们用于构造或改变另一个对象。下表总结了何时可以参观属性。

¥There are many methods in JavaScript that traverse a group of properties of an object. Sometimes, these properties are returned as an array; sometimes, they are iterated one-by-one in a loop; sometimes, they are used for constructing or mutating another object. The following table summarizes when a property may be visited.

仅访问字符串属性或仅访问符号属性的方法将有一个额外的注释。✅ 表示将访问该类型的属性;❌ 意味着不会。

¥Methods that only visit string properties or only symbol properties will have an extra note. ✅ means a property of this type will be visited; ❌ means it will not.

可枚举,拥有 可枚举、继承 不可枚举,拥有 不可枚举,继承
Object.keys
Object.values
Object.entries
✅ br />(字符串)
Object.getOwnPropertyNames ✅ br />(字符串) ✅ br />(字符串)
Object.getOwnPropertySymbols ✅ br />(符号) ✅ br />(符号)
Object.getOwnPropertyDescriptors
Reflect.ownKeys
for...in ✅ br />(字符串) ✅ br />(字符串)
Object.assign
(第一个参数之后)
对象传播

通过可枚举性/所有权获取属性

¥Obtaining properties by enumerability/ownership

请注意,这并不是适用于所有情况的最有效算法,但对于快速演示很有用。

¥Note that this is not the most efficient algorithm for all cases, but useful for a quick demonstration.

  • 检测可以通过 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;
  },
};

也可以看看