Книга: Выразительный JavaScript

Нежелательное взаимодействие прототипов

Нежелательное взаимодействие прототипов

Прототип помогает в любое время добавлять новые свойства и методы всем объектам, которые основаны на нём. К примеру, нашим кроликам может понадобиться танец.

Rabbit.prototype.dance = function() {
  console.log("А " + this.type + " кролик танцует джигу.");
};
killerRabbit.dance();
// ? А убийственный кролик танцует джигу.

Это удобно. Но в некоторых случаях это приводит к проблемам. В предыдущих главах мы использовали объект как способ связать значения с именами – мы создавали свойства для этих имён, и давали им соответствующие значения. Вот пример из 4-й главы:

var map = {};
function storePhi(event, phi) {
  map[event] = phi;
}
storePhi("пицца", 0.069);
storePhi("тронул дерево", -0.081);

Мы можем перебрать все значения фи в объекте через цикл for/in, и проверить наличие в нём имени через оператор in. К сожалению, нам мешается прототип объекта.

Object.prototype.nonsense = "ку";
for (var name in map)
  console.log(name);
// ? пицца
// ? тронул дерево
// ? nonsense
console.log("nonsense" in map);
// ? true
console.log("toString" in map);
// ? true
// Удалить проблемное свойство
delete Object.prototype.nonsense;

Это же неправильно. Нет события под названием “nonsense”. И тем более нет события под названием “toString”.

Занятно, что toString не вылезло в цикле for/in, хотя оператор in возвращает true на его счёт. Это потому, что JavaScript различает счётные и несчётные свойства.

Все свойства, которые мы создаём, назначая им значение – счётные. Все стандартные свойства в Object.prototype – несчётные, поэтому они не вылезают в циклах for/in.

Мы можем объявить свои несчётные свойства через функцию Object.defineProperty, которая позволяет указывать тип создаваемого свойства.

Object.defineProperty(Object.prototype, "hiddenNonsense", {
  enumerable: false, value: "ку"
});
for (var name in map)
  console.log(name);
// ? пицца
// ? тронул дерево
console.log(map.hiddenNonsense);
// ? ку

Теперь свойство есть, а в цикле оно не вылезает. Хорошо. Но нам всё ещё мешает проблема с оператором in, который утверждает, что свойства Object.prototype присутствуют в нашем объекте. Для этого нам понадобится метод hasOwnProperty.

console.log(map.hasOwnProperty("toString"));
// ? false

Он говорит, является ли свойство свойством объекта, без оглядки на прототипы. Часто это более полезная информация, чем выдаёт оператор in.

Если вы волнуетесь, что кто-то другой, чей код вы загрузили в свою программу, испортил основной прототип объектов, я рекомендую писать циклы for/in так:

for (var name in map) {
  if (map.hasOwnProperty(name)) {
    // ... это наше личное свойство
  }
}

Оглавление книги


Генерация: 1.677. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз