В этом посте рассматривается влияние наследования и перечисления на операции со свойствами в 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