В Java, C++ и других объектно_ориентированных языках на базе классов имеет_ ся явная концепция иерархии классов. Каждый класс может иметь надкласс, от которого он наследует свойства и методы. Любой класс может быть расширен, т. е. иметь подкласс, наследующий его поведение. Как мы видели, JavaScript поддерживает наследование прототипов вместо наследования на базе классов. Тем не менее в JavaScript могут быть проведены аналогии с иерархией классов.
В JavaScript класс Object – это наиболее общий класс, и все другие классы явля_ ются его специализированными версиями, или подклассами. Можно также ска_ зать, что Object – это надкласс всех встроенных классов. Все классы наследуют несколько базовых методов класса Object.
Мы узнали, что объекты наследуют свойства от объекта_прототипа их конструк_ тора. Как они могут наследовать свойства еще и от класса Object? Вспомните, что объект_прототип сам представляет собой объект; он создается с помощью конст_
1 Но при таком определении достаточно «странную» семантику обретают операто_ ры отношения < и >. – Примеч. науч. ред.
9.5. Надклассы и подклассы
руктора Object(). Это значит, что объект_прототип наследует свойства от Ob_ ject.prototype! Поэтому объект класса Complex наследует свойства от объекта Com_ plex.prototype, который в свою очередь наследует свойства от Object.prototype. Когда выполняется поиск некоторого свойства в объекте Complex, сначала выпол_ няется поиск в самом объекте. Если свойство не найдено, поиск продолжается в объекте Complex.prototype. И наконец, если свойство не найдено и в этом объек_ те, выполняется поиск в объекте Object.prototype.
Обратите внимание: поскольку в объекте_прототипе Complex поиск происходит раньше, чем в объекте_прототипе Object, свойства объекта Complex.prototype скрывают любые свойства с тем же именем из Object.prototype. Так, в классе, по_ казанном в примере 9.2, мы определили в объекте Complex.prototype метод toString(). Object.prototype также определяет метод с этим именем, но объекты Complex никогда не увидят его, поскольку определение toString() в Complex.proto_ type будет найдено раньше.
Все классы, которые мы показали в этой главе, представляют собой непосредст_ венные подклассы класса Object. Это типично для программирования на Java_ Script; обычно в создании более сложной иерархии классов нет никакой необхо_ димости. Однако когда это требуется, можно создать подкласс любого другого класса. Предположим, что мы хотим создать подкласс класса Rectangle, чтобы до_ бавить в него свойства и методы, связанные с координатами прямоугольника. Для этого мы просто должны быть уверены, что объект_прототип нового класса сам является экземпляром Rectangle и потому наследует все свойства Rectang_ le.prototype. Пример 9.3 повторяет определение простого класса Rectanle и затем расширяет это определение за счет создания нового класса PositionedRectangle.
Пример 9.3. Создание подкласса в JavaScript
// Определение простого класса прямоугольников.
// Этот класс имеет ширину и высоту и может вычислять свою площадь function Rectangle(w, h) {
// Поскольку объект_прототип был создан с помощью конструктора
// Rectangle(), свойство constructor в нем ссылается на этот
// конструктор. Но нам нужно, чтобы объекты PositionedRectangle
// ссылались на другой конструктор, поэтому далее выполняется
// присваивание нового значения свойству constructor PositionedRectangle.prototype.constructor = PositionedRectangle;
// Теперь у нас имеется правильно настроенный прототип для нашего
// подкласса, можно приступать к добавлению методов экземпляров. PositionedRectangle.prototype.contains = function(x,y) {
return (x > this.x && x < this.x + this.width && y > this.y && y < this.y + this.height);
}
Как видно из примера 9.3, создание подклассов в JavaScript выглядит более сложным, чем наследование от класса Object. Первая проблема связана с необхо_ димостью вызова конструктора надкласса из конструктора подкласса, причем конструктор надкласса приходится вызывать как метод вновь созданного объек_ та. Затем приходится хитрить и подменять конструктор объекта_прототипа под_ класса. Нам потребовалось явно создать этот объект_прототип как экземпляр надкласса, после чего надо было явно изменить свойство constructor объекта_ прототипа.1 Может также появиться желание удалить любые свойства, которые создаются конструктором надкласса в объекте_прототипе, поскольку очень важ_ но, чтобы свойства объекта_прототипа наследовались из его прототипа.
Имея такое определение класса PositionedRectangle, его можно использовать в сво_ их программах примерно так:
var r = new
PositionedRectangle(2,2,2,2);
print(r.contains(3,3)); //
Вызывается
метод экземпляра
print(r.area( ));
//
Вызывается
унаследованный метод экземпляра
// Работа
с
полями
экземпляра
класса:
print(r.x
+
", " +
r.y + ", "
+ r.width + ", " + r.height);
// Наш объект может рассматриваться как экземпляр всех 3 классов
1 В версии Rhino 1.6r1 и более ранних (интерпретатор JavaScript, написанный на языке Java) имеется ошибка, которая делает свойство constructor неудаляемым и доступным только для чтения. В этих версиях Rhino в программном коде, вы_ полняющем настройку свойства constructor, происходит сбой без вывода сообще_ ний об ошибке. В результате экземпляры класса PositionedRectangle наследуют значение свойства constructor, которое ссылается на конструктор Rectangle(). На практике эта ошибка почти не проявляется, потому что свойства наследуются правильно и оператор instanceof корректно различает экземпляры классов Positi_ onedRectangle и Rectangle.
9.5. Надклассы и подклассы
print(r instanceof PositionedRectangle && r instanceof Rectangle &&