Статьи

Свойства JavaScript: наследование и перечислимость

В этом посте рассматривается влияние наследования и перечисления на операции со свойствами в JavaScript.

Виды недвижимости

В принципе, объекты в JavaScript — это простые карты (словари) из строк в значения. Однако два фактора усложняют ситуацию:

  • Собственные и наследственные свойства. Объект может указывать на свой прототип через внутреннее свойство. Цепь прототипа представляет собой последовательность объектов , которая начинается с объектом, продолжает свой прототип, прототип прототипа и т.д. Многие операции рассмотреть все свойства в цепи прототипов, некоторые операции , рассмотрят только собственные свойства, которые хранятся в первом объекте прототип цепи.
  • Перечислимые свойства. Вы можете скрыть свойства от некоторых операций, сделав их не перечисляемыми. Перечислимость является одним из трех атрибутов свойства: возможность записи, перечислимость, конфигурируемость [1].

Доступ к свойствам

Существуют следующие операции для доступа к свойствам:

  1. Перечислим собственные свойства.
    Получить имена свойств:
        Object.keys(obj)
    
  2. Все собственные свойства.
    Получить / обнаружить имена свойств:
        Object.getOwnPropertyNames(obj)
        Object.hasOwnProperty(obj, propName)
    

    Получить значение свойства:

        Object.getOwnPropertyDescriptor(obj)
    

    Установите значения свойств, удалите свойства (влияет только на первый объект в цепочке прототипов):

        obj.propName = value
        obj["propName"] = value
    
        delete obj.propName
        delete obj["propName"]
    
        Object.defineProperty(obj, propName, desc)
        Object.defineProperties(obj, descObj)
    
  3. Перечислимые унаследованные свойства.
    Получить имена свойств:
        for (propName in obj)
    
  4. Все унаследованные свойства.
    Определить имя свойства:
        propName in obj
    

    Прочитайте значение свойства:

        obj.propName
        obj["propName"]
    

Использование объекта в качестве карты

Объекты часто используются в качестве отображения строк или значений или наборов строк. При этом следует соблюдать осторожность: почти каждый объект имеет прототип Object.prototype и, таким образом, наследует множество свойств:

    > "valueOf" in {}
    true
    > "toString" in {}
    true

С ECMAScript 5 вы используете операции из (1) и (2), и все в порядке.

    > var proto = { foo: 123 };
    > var obj = Object.create(proto);
    > obj.hasOwnProperty("foo")
    false

Однако до ECMAScript 5 люди часто использовали операции из (3) и (4), и это вызывает проблемы:

    > for (var p in obj) console.log(p);
    foo
    > "foo" in obj
    true

Если вы сделаете свойство prototype не перечисляемым, вы можете исправить цикл for-in, но не оператор in:

    > var proto = {};
    > Object.defineProperty(proto, "foo", { enumerable: false, value: 123 });
    {}
    > var obj = Object.create(proto);
    > for (var p in obj) console.log(p);
    > "foo" in obj
    true

Перечислимость и стандартная библиотека

В JavaScript многие свойства не перечисляются, особенно все свойства прототипов. Единственная причина этого заключается в том, чтобы скрыть их от-в. Давайте рассмотрим, что скрывает JavaScript, используя следующие две вспомогательные функции.

    /** Return an array with the names of the inherited enumerable properties of obj */
    function inheritedEnumerablePropertyNames(obj) {
        var result = [];
        for (var propName in obj) {
            result.push(propName);
        }
        return result;
    }

    /** Return an array with the names of the inherited properties of obj */
    function inheritedPropertyNames(obj) {
        if ((typeof obj) !== "object") { // null is not a problem
            throw new Error("Only objects are allowed");
        }
        var props = {};
        while(obj) {
            Object.getOwnPropertyNames(obj).forEach(function(p) {
                props[p] = true;
            });
            obj = Object.getPrototypeOf(obj);
        }
        return Object.getOwnPropertyNames(props);
    }

Объекты: все несобственные свойства не перечисляются.

    > inheritedPropertyNames({ foo: "abc" })
    [ 'foo',
      'constructor',
      'toString',
      ...
      '__lookupSetter__' ]
    > inheritedEnumerablePropertyNames({ foo: "abc" })
    [ 'foo' ]

Массивы: все несобственные свойства и длина не перечисляются.

    > inheritedPropertyNames([ "abc" ])
    [ '0',
      'length',
      'constructor',
      'concat',
      ...
      '__lookupSetter__' ]
    > inheritedEnumerablePropertyNames([ "abc" ])
    [ '0' ]

Обратите внимание, что это может дать вам идею, что вы можете использовать for-in для перебора индексов в массиве. Однако это не рекомендуется, потому что это не будет работать должным образом, если кто-то добавит (неиндексное) свойство в массив.

Лучшие практики

Следующие рекомендации касаются методов ECMAScript 5. Используйте шим, чтобы получить эти методы в старых браузерах [2].

Программисты JavaScript:

  • Если вы используете объект в качестве карты, работайте только с собственными свойствами, например, с помощью методов ECMAScript 5 Object.getOwnPropertyNames () и Object.hasOwnProperty ().
  • Перебор объектов и массивов: см. [4].

Авторы API:

  • При добавлении свойств во встроенные прототипы [3] используйте Object.defineProperty () и аналогичные методы, чтобы сделать их не перечисляемыми. Это даст вам некоторую защиту от прерывания циклов for-in в устаревшем коде.
  • С вашими собственными типами вам не нужно быть таким осторожным, потому что вы можете ожидать, что новый код будет игнорировать унаследованные свойства при использовании объектов в качестве карт.

Будущее:

Связанное чтение

  1. Джон Резиг — ECMAScript 5 Объекты и свойства
  2. es5-shim: используйте ECMAScript 5 в старых браузерах
  3. « Разрешено все: расширение встроенных модулей » [видео]. Разговор Эндрю Дюпона на JSConf 2011. Вдохновил этот пост. Спасибо Брендану Эйчу за указатель.
  4. Перебор массивов и объектов в JavaScript

 

С http://www.2ality.com/2011/07/js-properties.html