Перелічуваність і власність властивостей

Кожна властивість об'єктів у JavaScript може бути класифікована за трьома критеріями:

  • Перелічувана або неперелічувана;
  • Рядок або символ;
  • Власна властивість або властивість, успадкована з ланцюжка прототипів.

Перелічувані властивості – ті властивості, чия прихована позначка перелічуваності має значення істинності, що є усталеним для властивостей, створених шляхом простого присвоєння або ініціалізатора властивостей. Властивості, означені за допомогою 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
(символи)

(symbols)
Object.getOwnPropertyDescriptors
Reflect.ownKeys
for...in
(рядки)

(strings)
Object.assign
(Після першого параметра)
Object spread

Отримання властивостей за їхньою перелічуваністю чи власністю

Зверніть увагу, що це не найефективніший алгоритм для всіх випадків, зате корисний для швидкої демонстрації.

  • Перевірка може відбутися за SimplePropertyRetriever.theGetMethodYouWant(obj).includes(prop)
  • Ітерування може відбутися за SimplePropertyRetriever.theGetMethodYouWant(obj).forEach((value, prop) => {}); (або використайте filter(), map() тощо)
const SimplePropertyRetriever = {
  getOwnEnumProps(obj) {
    return this._getPropertyNames(obj, true, false, this._enumerable);
    // Або можна було б використати for...in, відфільтрований за Object.hasOwn, або просто таке: return Object.keys(obj);
  },
  getOwnNonEnumProps(obj) {
    return this._getPropertyNames(obj, true, false, this._notEnumerable);
  },
  getOwnProps(obj) {
    return this._getPropertyNames(
      obj,
      true,
      false,
      this._enumerableAndNotEnumerable,
    );
    // Або просто використайте: return Object.getOwnPropertyNames(obj);
  },
  getPrototypeEnumProps(obj) {
    return this._getPropertyNames(obj, false, true, this._enumerable);
  },
  getPrototypeNonEnumProps(obj) {
    return this._getPropertyNames(obj, false, true, this._notEnumerable);
  },
  getPrototypeProps(obj) {
    return this._getPropertyNames(
      obj,
      false,
      true,
      this._enumerableAndNotEnumerable,
    );
  },
  getOwnAndPrototypeEnumProps(obj) {
    return this._getPropertyNames(obj, true, true, this._enumerable);
    // Або ж можна використати невідфільтрований for...in
  },
  getOwnAndPrototypeNonEnumProps(obj) {
    return this._getPropertyNames(obj, true, true, this._notEnumerable);
  },
  getOwnAndPrototypeEnumAndNonEnumProps(obj) {
    return this._getPropertyNames(
      obj,
      true,
      true,
      this._enumerableAndNotEnumerable,
    );
  },
  // Функції зворотного виклику для перевірки приватних статичних властивостей
  _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;
  },
  // Натхненно 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;
  },
};

Дивіться також