В этом посте рассматривается влияние наследования и перечисления на операции со свойствами в JavaScript.
Виды недвижимости
В принципе, объекты в JavaScript — это простые карты (словари) из строк в значения. Однако два фактора усложняют ситуацию:
- Собственные и наследственные свойства. Объект может указывать на свой прототип через внутреннее свойство. Цепь прототипа представляет собой последовательность объектов , которая начинается с объектом, продолжает свой прототип, прототип прототипа и т.д. Многие операции рассмотреть все свойства в цепи прототипов, некоторые операции , рассмотрят только собственные свойства, которые хранятся в первом объекте прототип цепи.
- Перечислимые свойства. Вы можете скрыть свойства от некоторых операций, сделав их не перечисляемыми. Перечислимость является одним из трех атрибутов свойства: возможность записи, перечислимость, конфигурируемость [1].
Доступ к свойствам
Существуют следующие операции для доступа к свойствам:
- Перечислим собственные свойства.
Получить имена свойств:Object.keys(obj)
- Все собственные свойства.
Получить / обнаружить имена свойств: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)
- Перечислимые унаследованные свойства.
Получить имена свойств:for (propName in obj)
- Все унаследованные свойства.
Определить имя свойства: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 в устаревшем коде.
- С вашими собственными типами вам не нужно быть таким осторожным, потому что вы можете ожидать, что новый код будет игнорировать унаследованные свойства при использовании объектов в качестве карт.
Будущее:
- ECMAScript.next будет иметь специальный тип для карт. Таким образом, нам больше не придется (ab) использовать объекты в качестве карт.
Связанное чтение
- Джон Резиг — ECMAScript 5 Объекты и свойства
- es5-shim: используйте ECMAScript 5 в старых браузерах
- « Разрешено все: расширение встроенных модулей » [видео]. Разговор Эндрю Дюпона на JSConf 2011. Вдохновил этот пост. Спасибо Брендану Эйчу за указатель.
- Перебор массивов и объектов в JavaScript