Предыдущее обсуждение проблемы создания подклассов описывает порядок создания новых классов, наследующих методы других классов. Язык JavaScript настолько гибкий, что создание подклассов и использование механизма насле_ дования – это не единственный способ расширения функциональных возможно_ стей классов. Поскольку функции в JavaScript – это просто значения данных, они могут легко копироваться (или «заимствоваться») из одного класса в дру_ гой. В примере 9.4 демонстрируется функция, которая заимствует все методы одного класса и создает их копии в объекте_прототипе другого класса.
Пример 9.4. Заимствование методов одного класса для использования в другом
// Заимствование методов одного класса для использования в другом.
// Аргументы должны быть функциями_конструкторами классов.
// Методы встроенных типов, таких как Object, Array, Date и RegExp
// не являются перечислимыми и потому не заимствуются этой функцией. function borrowMethods(borrowFrom, addTo) {
var from = borrowFrom.prototype; // прототип_источник
9.6. Расширение без наследования
var to = addTo.prototype;
// прототип_приемник
for(m in from) { // Цикл по всем свойствам прототипа_источника
if (typeof from[m] != "function") continue; //
Игнорировать все,
//
что не является функциями
to[m] = from[m];
//
Заимствовать метод
}
}
Многие методы настолько тесно связаны с классом, в котором они определены, что нет смысла пытаться использовать их в другом классе. Однако некоторые методы могут быть достаточно универсальными и пригодяться в любом классе. В примере 9.5 приводятся определения двух классов, ничего особо полезного не делающих, зато реализующих методы, которые могут быть заимствованы дру_ гими классами. Подобные классы, разрабатываемые специально с целью заим_ ствования, называются классами'смесями, или просто смесями.
Пример 9.5. Классы'смеси с универсальными методами, предназначенными для заимствования
// Сам по себе этот класс не очень хорош. Но он определяет универсальный
// метод toString(), который может представлять интерес для других классов. function GenericToString() {}
GenericToString.prototype.toString = function( ) { var props = [];
for(var name in this) {
if (!this.hasOwnProperty(name)) continue; var value = this[name];
var s = name + ":" switch(typeof value) { case 'function':
s += "function"; break;
case 'object':
if (value instanceof Array) s += "array" else s += value.toString( );
break;
default:
s += String(value); break;
}
props.push(s);
}
return "{" + props.join(", ") + "}";
}
// Следующий класс определяет метод equals(), который сравнивает простые объекты. function GenericEquals() {}
GenericEquals.prototype.equals = function(that) { if (this == that) return true;
// объекты равны, только если объект this имеет те же свойства,
// что и объект that, и не имеет никаких других свойств
// Обратите внимание: нам не требуется глубокое сравнение.
// Значения просто должны быть === друг другу. Из этого следует,
188 Глава 9. Классы, конструкторы и прототипы
// если есть свойства, ссылающиеся на другие объекты, они должны ссылаться
// на те же самые объекты, а не на объекты, для которых equals() возвращает true var propsInThat = 0;
for(var name in that) { propsInThat++;
if (this[name] !== that[name]) return false;
}
// Теперь необходимо убедиться, что объект this не имеет дополнительных свойств var propsInThis = 0;
for(name in this) propsInThis++;
// Если объект this обладает дополнительными свойствами,
// следовательно, объекты не равны
if (propsInThis != propsInThat) return false;
// Два объекта выглядят равными. return true;
}
Вот как выглядит простой класс Rectangle, который заимствует методы toString() и equals(), определенные в классах_смесях:
// Простой класс Rectangle function Rectangle(x, y, w, h) {
Ни один из представленных здесь классов_смесей не имеет собственного конст_ руктора, однако это не значит, что конструкторы нельзя заимствовать. В сле_ дующем фрагменте приводится определение нового класса с именем ColoredRec_ tangle. Он наследует функциональность класса Rectangle и заимствует конструк_ тор и метод из класса_смеси Colored:
// Эта смесь содержит метод, зависящий от конструктора. Оба они,
// и конструктор, и метод должны быть заимствованы.
// Настройка объекта_прототипа на наследование методов от Rectangle ColoredRectangle.prototype = new Rectangle(); ColoredRectangle.prototype.constructor = ColoredRectangle; ColoredRectangle.prototype.superclass = Rectangle;
9.7. Определение типа объекта
// Заимствовать методы класса Colored в новый класс borrowMethods(Colored, ColoredRectangle);
Класс ColoredRectangle расширяет класс Rectangle (и наследует его методы), а так_ же заимствует методы класса Colored. Сам класс Rectangle наследует класс Object и заимствует методы классов GenericEquals и GenericToString. Хотя подобные ана_ логии здесь неуместны, можно воспринимать это как своего рода множественное наследование. Так как класс ColoredRectangle заимствует методы класса Colored, экземпляры класса ColoredRectangle можно одновременно рассматривать как эк_ земпляры класса Colored. Оператор instanceof не сможет сообщить об этом, но в разделе 9.7.3 мы создадим более универсальный метод, который позволит оп_ ределять, наследует или заимствует некоторый объект методы заданного класса.