Всегда смущала обычная реализации наследования в JavaScript, а в особенности вызов метод базового класса из метода потомка. Здесь и в дальнейшем я буду использовать термин «класс» из классического ООП, хотя в JavaScript заложена немного другая концепция.
Допустим, что у нас есть иерархия классов вида A → B → C и в методе наследников нам нужно вызывать тот же метод базового класса вверх по иерархии. Обычно это выглядит примерно так (вариантов может быть несколько, но смысл не меняется):
// описание методов класса A
...
method : function(param1, param2) {
// действия класса A
}
...
// описание методов класса B
...
method : function(param1, param2) {
// действия базового класса A
A.prototype.method.call(this, param1, param2);
// действия класса B
}
...
// описание методов класса С
...
method : function(param1, param2) {
// действия базового класса B
B.prototype.method.call(this, param1, param2);
// действия класса C
}
...
Таким образом, для вызова метода базового класса приходится сооружать такую неуклюжую конструкцию, где каждый раз упоминается имя базового класса и имя метода, где приходится вызывать call
, а в параметрах передавать this
. Вообщем, мягко говоря, не очень красиво. Ведь было гораздо красивее написать так:
// описание методов класса B
...
method : function(param1, param2) {
// действия базового класса A
this.__base(param1, param2);
// действия класса B
}
...
// описание методов класса С
...
method : function(param1, param2) {
// действия базового класса B
this.__base(param1, param2);
// действия класса C
}
...
Возможно ли такое в JavaScript? Оказывается, возможно.
Читая блог Dean Edwards’а, я наткнулся на интересную идею реализации подобного функционала — при наследовании не просто записывать в прототип нового класса перекрываемый метод, а заворачивать его в метод-обертку, которая будет перед вызовом метода заменять this.__base
на необходимый метод базового класса, а после вызова восстанавливать (потому что возможно ситуация когда перекрываемый метод вызывает другие методы, которые также могут вызывать одноименные методы базовых классов). Тоже самое можно сделать и для конструктора.
В итоге, наследование у меня происходит сейчас так:
var SubClass = BaseClass.inheritTo(
{
// если нужно, перекрываем конструктор
__constructor : function(param) {
// если нужно, вызываем конструктор базового класса
this.__base(param);
// и делаем что-нибудь еще
},
// перекрываемый метод
method : function(param) {
// если нужно, вызываем метод method базового класса
this.__base(param);
// и делаем что-нибудь еще
}
},
{
// если необходимо, тут перекрываем статические свойства и методы
}
);
Для единообразия базовый класс создается так (наследуется от абстрактного класса Abstract
):
var Class = Abstract.inheritTo(
{
// конструктор
__constructor : function(param) {
},
// обычный метод
method : function(param) {
}
},
{
// если необходимо, тут создаем статические свойства и методы
}
);
Работоспособность этого способа была проверена на иерархии классов ZForms (около 30 классов). После рефакторинга код стал намного стройнее, короче и понятнее.