Преимущества не перечисляемых свойств в JavaScript
Энумерация является одной из трех характеристик свойства: запись, энумерация и конфигурируемость. У меня есть несколько вопросов:
- В чем преимущества создания свойств неэнумерируемыми в JavaScript? Я понимаю, что мы скрываем свойство, делая его неэнумерируемым, но какие выгоды от скрытия свойств?
- Можем ли мы получить доступ к неэнумерируемым свойствам? Если да, то в чем выгода от их неэнумерабельности?
- Все ли предопределенные свойства объектов установлены как неэнумерируемые? Например, являются ли такие методы массива, как
pop
иpush
, неэнумерируемыми?
4 ответ(ов)
Основное преимущество использования Object.defineProperty
заключается в том, что вы можете контролировать, какие свойства будут отображаться при перечислении свойств объекта, например, при использовании конструкции for in
или метода Object.keys()
.
MDN хорошо объясняет это с помощью Object.defineProperty
: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Обычно, когда разработчики хотят добавить метод к Object
, например, полифил для некоторого метода, не поддерживаемого старыми браузерами, они изменяют свойство .prototype
. Однако это делает новое свойство перечисляемым, и тем самым может осложнить результат, получаемый в циклах или при сборе ключей (особенно без использования .hasOwnProperty
, который не все применяют).
Таким образом, вместо кода вроде:
Object.prototype.myMethod = function () {
alert("Ах!");
};
вы можете воспользоваться Object.defineProperty
, чтобы явно указать, что данный метод не должен быть перечисляемым:
Object.defineProperty(Object.prototype, 'myMethod', {
value: function () {
alert("Ах!");
},
enumerable: false
});
Таким образом, например, когда вы используете for (var key in obj)
, "myMethod" не будет включен в перечисление, и вам не придется беспокоиться о необходимости использования .hasOwnProperty
. Основная проблема в этом заключается в том, что некоторые браузеры могут не поддерживать эту функциональность: http://kangax.github.com/es5-compat-table/, и не все библиотеки или код используют её, поэтому вы не всегда можете полагаться на внешние библиотеки или код, которые корректно работают с этим.
Вы можете в любой момент получить доступ к не перечисляемому свойству, оно просто не будет показано при перечислении свойств объекта — это ключевой момент.
Я также считаю, что все "предопределенные" свойства объектов являются не перечисляемыми. При этом я имею в виду исключительно нативные свойства, а не обязательно унаследованные или созданные вручную. Например, pop
и push
не будут перечисляться, но Array.prototype.indexOf
будет, если он создан как полифил в старом браузере, который не поддерживает этот метод... это, конечно, можно избежать, если использовать Object.defineProperty
, как в моем примере выше. Другой пример — это свойство length
, которое также не будет перечисляться.
Вот общий пример: http://jsfiddle.net/aHJ3g/
Использование и определение Object.keys
важно: "Возвращает массив собственных перечисляемых свойств данного объекта в том же порядке, что и при использовании цикла for-in
(разница в том, что цикл for-in
также перечисляет свойства в цепочке прототипов)." - из MDN - https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
Одним из основных преимуществ, как я вижу, является то, что оно предотвращает загрязнение публичного пространства имен приватными свойствами объекта.
Предположим, вы создали и опубликовали мощную библиотеку под названием Cosmos
. Пользователь открывает интерпретатор Node и создает новый экземпляр, вызывая конструктор:
var Cosmos = require('Cosmos');
var cosmos = new Cosmos('my empire');
Теперь пользователь просто набирает cosmos
и нажимает Enter, чтобы увидеть, какой публичный API он поддерживает. Какой из двух вариантов вы хотите, чтобы пользователь увидел?
{ name: 'my empire',
grow: [Function: grow],
addStar: [Function: addStar],
beautify: [Function: beautify],
implode: [Function: implode],
destroy: [Function: destroy] }
ИЛИ
{ _age: 25000,
_size: 35000,
_destroyed: false,
name: 'my empire',
_numStars: 200,
_init: [Function: _init],
grow: [Function: grow],
_grow: [Function: _grow],
addStar: [Function: addStar],
_checkStatus: [Function: _checkStatus],
beautify: [Function: beautify],
implode: [Function: implode],
destroy: [Function: destroy] }
Очевидно, предпочтительнее первый вариант, так как он менее загроможден и предоставляет пользователю четкое представление о доступном интерфейсе. Скрытие приватных свойств помогает сосредоточиться на публичных методах и свойствах, что повышает удобство использования вашей библиотеки.
Вы можете сделать свойство не перечисляемым, чтобы получить к нему доступ. Однако, когда вы используете цикл for...in
по объекту, не перечисляемое свойство не будет итерироваться.
Например, обратите внимание на первый пункт.
Свойства, унаследованные от родителя, являются перечисляемыми (при условии, что они помечены как перечисляемые):
var x = {a: 1, b: 2}; // a и b являются перечисляемыми свойствами по умолчанию
x.propertyIsEnumerable("toString"); // вернет false, так как оно не помечено как перечисляемое
var y = Object.create(x);
y.c = 3;
for (p in y) console.log(p); // этот цикл выведет c, a и b, но не toString
Таким образом, свойство toString
не будет отображаться в цикле, хотя к нему все еще можно получить доступ непосредственно через объект.
Вы можете использовать свойство array.length
для получения длины массива, но если вы хотите избежать его использования в циклах или обходах, вы можете сохранить длину массива в отдельной переменной. Это позволит вам избежать повторного вычисления длины массива при каждой итерации:
let arr = [1, 2, 3, 4, 5];
let length = arr.length;
for (let i = 0; i < length; i++) {
console.log(arr[i]);
}
Таким образом, вы один раз получаете длину массива, сохраняете ее в переменной length
и используете эту переменную в цикле, что может немного повысить производительность и сделать код более чистым.
Использование 'prototype' и 'this' в JavaScript?
Как перенаправить на другую веб-страницу?
Где найти документацию по форматированию даты в JavaScript?
Как проверить, содержит ли массив строку в TypeScript?
Ссылка и выполнение внешнего JavaScript-файла, размещенного на GitHub