Enumerability and ownership of properties

В языке JavaScript свойства объектов могут быть перечисляемыми или неперечисляемыми (встречается вариант перевода: счётные или несчётные). Если внутреннему флагу [[Enumerable]] свойства присвоить значение true, то данное свойство становится перечисляемым. Это происходит по умолчанию для свойств, созданных простым присваиванием или через инициализацию свойств (свойства, определённые через Object.defineProperty получают по умолчанию значение флага [[Enumerable]] равным false). Перечисляемые свойства участвуют в итерации в цикле for...in, если только имя свойства не Символ. Принадлежность свойства определяется тем, принадлежит ли оно непосредственно объекту или получено из цепочки прототипов. Также можно получить весь список свойств объекта. Ниже, в таблице, указаны возможные способы нахождения, получения и итерации свойств объектов. Некоторые из них нельзя использовать без дополнительного кода, примеры которого приведены после таблицы.

Перечисляемость и принадлежность свойств - встроенные методы определения, получения и итерации
Назначение Свойства объекта Свойства объекта и его прототипов Свойства из цепочки прототипов
Определение
Перечисляемые Неперечисляемые Перечисляемые и Неперечисляемые
propertyIsEnumerable (en-US) hasOwnProperty и !propertyIsEnumerable (en-US) hasOwnProperty
Недоступно без дополнительного кода Недоступно без дополнительного кода
Получение
Перечисляемые Неперечисляемые Перечисляемые и Неперечисляемые
Object.keys (en-US) Получить getOwnPropertyNames (en-US), выбрать свойства, не удовлетворяющие propertyIsEnumerable (en-US) getOwnPropertyNames (en-US)
Недоступно без дополнительного кода Недоступно без дополнительного кода
Итерация
Перечисляемые Неперечисляемые Перечисляемые и Неперечисляемые
Итерация по массиву Object.keys (en-US) Итерация по getOwnPropertyNames (en-US), с выбранными свойствами, не удовлетворяющими propertyIsEnumerable (en-US) Итерация по getOwnPropertyNames (en-US)
Перечисляемые Неперечисляемые Перечисляемые и Неперечисляемые
for..in (en-US) Недоступно без дополнительного кода Недоступно без дополнительного кода
Недоступно без дополнительного кода

Доступ к свойствам по их перечисляемости/принадлежности

Хотим заметить, что данный алгоритм эффективен не для всех классов.

  • Определение свойства: SimplePropertyRetriever.theGetMethodYouWant(obj).indexOf(prop) > -1
  • Итерация: SimplePropertyRetriever.theGetMethodYouWant(obj).forEach(function (value, prop) {}); (or use filter(), map(), etc.)
js
var SimplePropertyRetriever = {
  getOwnEnumerables: function (obj) {
    return this._getPropertyNames(obj, true, false, this._enumerable);
    // Или можно использовать for..in, отфильтрованный по hasOwnProperty или проще: return Object.keys(obj);
  },
  getOwnNonenumerables: function (obj) {
    return this._getPropertyNames(obj, true, false, this._notEnumerable);
  },
  getOwnEnumerablesAndNonenumerables: function (obj) {
    return this._getPropertyNames(
      obj,
      true,
      false,
      this._enumerableAndNotEnumerable,
    );
    // Или можно использовать: return Object.getOwnPropertyNames(obj);
  },
  getPrototypeEnumerables: function (obj) {
    return this._getPropertyNames(obj, false, true, this._enumerable);
  },
  getPrototypeNonenumerables: function (obj) {
    return this._getPropertyNames(obj, false, true, this._notEnumerable);
  },
  getPrototypeEnumerablesAndNonenumerables: function (obj) {
    return this._getPropertyNames(
      obj,
      false,
      true,
      this._enumerableAndNotEnumerable,
    );
  },
  getOwnAndPrototypeEnumerables: function (obj) {
    return this._getPropertyNames(obj, true, true, this._enumerable);
    // Или можно использовать for..in
  },
  getOwnAndPrototypeNonenumerables: function (obj) {
    return this._getPropertyNames(obj, true, true, this._notEnumerable);
  },
  getOwnAndPrototypeEnumerablesAndNonenumerables: function (obj) {
    return this._getPropertyNames(
      obj,
      true,
      true,
      this._enumerableAndNotEnumerable,
    );
  },
  // Private static property checker callbacks
  _enumerable: function (obj, prop) {
    return obj.propertyIsEnumerable(prop);
  },
  _notEnumerable: function (obj, prop) {
    return !obj.propertyIsEnumerable(prop);
  },
  _enumerableAndNotEnumerable: function (obj, prop) {
    return true;
  },
  // По мотивам http://stackoverflow.com/a/8024294/271577
  _getPropertyNames: function getAllPropertyNames(
    obj,
    iterateSelfBool,
    iteratePrototypeBool,
    includePropCb,
  ) {
    var props = [];

    do {
      if (iterateSelfBool) {
        Object.getOwnPropertyNames(obj).forEach(function (prop) {
          if (props.indexOf(prop) === -1 && includePropCb(obj, prop)) {
            props.push(prop);
          }
        });
      }
      if (!iteratePrototypeBool) {
        break;
      }
      iterateSelfBool = true;
    } while ((obj = Object.getPrototypeOf(obj)));

    return props;
  },
};

Определимость свойств

in for..in hasOwnProperty propertyIsEnumerable in Object.keys in Object.getOwnPropertyNames
Перечисляемые true true true true true true
Неперечисляемые true false true false false true
Унаследованные Перечисляемые true true false false false false
Унаследованные Неперечисляемые true false false false false false

Смотрите также