// → тронул дерево
// → 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)) {
// ... это наше личное свойство
}
}
Объекты без прототипов
Но кроличья нора на этом не заканчивается. А если кто-то зарегистрировал имя hasOwnProperty
в объекте map
и назначил ему значение 42? Теперь вызов map.hasOwnProperty
обращается к локальному свойству, в котором содержится номер, а не функция.
В таком случае прототипы только мешаются, и нам бы хотелось иметь объекты вообще без прототипов. Мы видели функцию Object.create
, что позволяет создавать объект с заданным прототипом. Мы можем передать null
для прототипа, чтобы создать свеженький объект без прототипа. Это то, что нам нужно для объектов типа map
, где могут быть любые свойства.
var map = Object.create(null);
map["пицца"] = 0.069;
console.log("toString" in map);
// → false
console.log("пицца" in map);
// → true
Так-то лучше! Нам уже не нужна приблуда hasOwnProperty
, потому что все свойства объекта заданы лично нами. Мы спокойно используем циклы for
/in
без оглядки на то, что люди творили с Object.prototype
.
Полиморфизм
Когда вы вызываете функцию String
, преобразующую значение в строку, для объекта, он вызовет метод toString
, чтобы создать осмысленную строчку. Я упомянул, что некоторые стандартные прототипы объявляют свои версии toString
для создания строк, более полезных, чем просто "[object Object]"
.
Это простой пример мощной идеи. Когда кусок кода написан так, чтобы работать с объектами через определённый интерфейс – в нашем случае через метод toString
– любой объект, поддерживающий этот интерфейс, можно подключить к коду, и всё будет просто работать.
Такая техника называется полиморфизм, хотя никто и не меняет своей формы. Полиморфный код может работать со значениями самых разных форм, пока они поддерживают одинаковый интерфейс.
Форматируем таблицу
Давайте рассмотрим пример, чтобы понять, как выглядит полиморфизм, да и вообще объектно-ориентированное программирование. Проект следующий: мы напишем программу, которая получает массив массивов из ячеек таблицы, и строит строку, содержащую красиво отформатированную таблицу. То есть, колонки и ряды выровнены. Типа вот этого:
name height country
------------ ------ -------------
Kilimanjaro 5895 Tanzania
Everest 8848 Nepal
Mount Fuji 3776 Japan
Mont Blanc 4808 Italy/France
Vaalserberg 323 Netherlands
Denali 6168 United States
Popocatepetl 5465 Mexico
Работать она будет так: основная функция будет спрашивать каждую ячейку, какой она ширины и высоты, и потом использует эту информацию для определения ширины колонок и высоты рядов. Затем она попросит ячейки нарисовать себя, и соберёт результаты в одну строку.