В главе 8 говорилось, что метод – это функция, которая вызывается как свойст_ во объекта. Когда функция вызывается таким способом, объект, посредством ко_ торого производится вызов, становится значением ключевого слова this. Пред_ положим, что необходимо рассчитать площадь прямоугольника, представленно_ го объектом Rectangle. Вот один из возможных способов:
function computeAreaOfRectangle(r) { return r.width * r.height; }
9.2. Прототипы и наследование
Эта функция прекрасно справляется с возложенными на нее задачами, но она не является объектно_ориентированной. Работая с объектом, лучше всего вызывать методы этого объекта, а не передавать объекты посторонним функциям в качест_ ве аргументов. Этот подход демонстрируется в следующем фрагменте:
// Создать объект Rectangle
var r = new Rectangle(8.5, 11); // Добавить к объекту метод
r.area = function() { return this.width * this.height; } // Теперь рассчитать площадь, вызвав метод объекта
var a = r.area();
Конечно же, не совсем удобно добавлять новый метод к объекту перед его ис_ пользованием. Однако ситуацию можно улучшить, если инициализировать свойство area в функции_конструкторе. Вот как выглядит улучшенная реализа_ ция конструктора Rectangle():
С новой версией конструктора тот же самый алгоритм можно реализовать по_ другому:
// Найти площадь листа бумаги формата U.S. Letter в квадратных дюймах
var r = new Rectangle(8.5, 11);
var a = r.area();
Такое решение выглядит гораздо лучше, но оно по_прежнему не является опти_ мальным. Каждый созданный прямоугольник будет иметь три свойства. Свойст_ ва width и height могут иметь уникальные значения для каждого прямоугольни_ ка, но свойство area каждого отдельно взятого объекта Rectangle всегда будет ссылаться на одну и ту же функцию (разумеется, это свойство можно изменить в процессе работы, но, как правило, предполагается, что методы объекта не должны меняться). Применение отдельных свойств для хранения методов объ_ ектов, которые могли бы совместно использоваться всеми экземплярами одного и того же класса, – это достаточно неэффективное решение.
Однако и эту проблему можно решить. Оказывается, все объекты в JavaScript содержат внутреннюю ссылку на объект, известный как прототип. Любые свой_ ства прототипа становятся свойствами другого объекта, для которого он являет_ ся прототипом. То есть, говоря другими словами, любой объект в JavaScript на' следует свойства своего прототипа.
В предыдущем разделе было показано, как оператор new создает пустой объект и затем вызывает функцию_конструктор. Но история на этом не заканчивается. После создания пустого объекта оператор new устанавливает в этом объекте ссыл_ ку на прототип. Прототипом объекта является значение свойства prototype функции_конструктора. Все функции имеют свойство prototype, которое ини_ циализируется в момент определения функции. Начальным значением этого свойства является объект с единственным свойством. Это свойство называется constructor и значением его является ссылка на саму функцию_конструктор,
168 Глава 9. Классы, конструкторы и прототипы
с которой этот прототип ассоциируется. (Описание свойства constructor приводи_ лось в главе 7, здесь же объясняется, почему каждый объект обладает свойством constructor.) Любые свойства, добавленные к прототипу, автоматически стано_ вятся свойствами объектов, инициализируемых конструктором.
Более понятно это можно объяснить на примере. Вот новая версия конструктора
Rectangle():
// Функция_конструктор инициализирует те свойства, которые могут
// иметь уникальные значения для каждого отдельного экземпляра. function Rectangle(w, h) {
this.width = w; this.height = h;
}
// Прототип объекта содержит методы и другие свойства, которые должны
// совместно использоваться всеми экземплярами этого класса. Rectangle.prototype.area = function() { return this.width * this.height; }
Конструктор определяет «класс» объектов и инициализирует свойства, такие как width и height, которые могут отличаться для каждого экземпляра класса. Объект_ прототип связан с конструктором, и каждый объект, инициализируемый конст_ руктором, наследует тот набор свойств, который имеется в прототипе. Это значит, что объект_прототип – идеальное место для методов и других свойств_констант.
Обратите внимание, что наследование осуществляется автоматически как часть процесса поиска значения свойства. Свойства не копируются из объекта_прото_ типа в новый объект; они просто присутствуют, как если бы были свойствами этих объектов. Это имеет два важных следствия. Во_первых, использование объ_ ектов_прототипов может в значительной степени уменьшить объем памяти, тре_ буемый для каждого объекта, т. к. объекты могут наследовать многие из своих свойств. Во_вторых, объект наследует свойства, даже если они были добавлены в прототип после создания объекта. Это означает наличие возможности добав_ лять новые методы к существующим классам (хотя это не совсем правильно).
Унаследованные свойства ничем не отличаются от обычных свойств объекта. Они поддаются перечислению в цикле for/in и могут проверяться с помощью опе_ ратора in. Отличить их можно только с помощью метода Object.hasOwnProperty():