Хочу рассказать о решении, которое я использую для одиночного наследования в JavaScript. Оно настолько маленькое, что наверняка в том или ином варианте встречается где-нибудь еще. Надеюсь, кому-то из читателей оно окажется полезным.
Это часть фреймворка, который я сделал для своей платформы Akshell, вот полный код решения и документация. Однако оно может пригодиться в любой Server-Side JavaScript среде, на стороне сервера удобное создание иерархий классов особенно актуально. Его можно использовать и на клиенте, если заменить работу со свойством
Собственно, все решение заключается в методе
Функция принимает два опциональных аргумента: конструктор и прототип создаваемого класса. Семантика передачи аргументов нестандартная, но в данном случае удобная: если первый аргумент является функцией, это конструктор, в противном случае — прототип.
Игрушечный пример иерархии классов:
Т.к. конструкторы являются объектами класса
Например, мы хотим, чтобы подклассы
Все подклассы
Конечно, метаклассы не стоит применять слишком широко, в частности,
Это часть фреймворка, который я сделал для своей платформы Akshell, вот полный код решения и документация. Однако оно может пригодиться в любой Server-Side JavaScript среде, на стороне сервера удобное создание иерархий классов особенно актуально. Его можно использовать и на клиенте, если заменить работу со свойством
__proto__
оператором new
.Собственно, все решение заключается в методе
subclass
класса Function
. Если позволяет среда, его стоит сделать non enumerable.Function.prototype.subclass = function (/* [constructor] [, prototype] */) {
var self = this;
var constructor = (
typeof(arguments[0]) == 'function'
? Array.prototype.shift.call(arguments)
: (this === Object
? function () {}
: function () { self.apply(this, arguments); }));
if (arguments[0])
constructor.prototype = arguments[0];
constructor.prototype.__proto__ = this.prototype;
constructor.__proto__ = this.__proto__;
return constructor;
};
Функция принимает два опциональных аргумента: конструктор и прототип создаваемого класса. Семантика передачи аргументов нестандартная, но в данном случае удобная: если первый аргумент является функцией, это конструктор, в противном случае — прототип.
Игрушечный пример иерархии классов:
var Figure = Object.subclass(
{
getArea: function () {
throw Error('Abstract');
}
});
var Rectangle = Figure.subclass(
function (a, b) {
this.a = a;
this.b = b;
},
{
getArea: function () {
return this.a * this.b;
}
});
var Square = Rectangle.subclass(
function (a) {
Rectangle.call(this, a, a);
});
Т.к. конструкторы являются объектами класса
Function
, то естественным образом подклассы Function
являются классами классов, или метаклассами. Определяя их можно управлять инстанциированием классов, а также определять статические свойства и методы классов, наследуемые подклассами (подкласс получает метакласс от своего родителя в предпоследней строке метода subclass
).Например, мы хотим, чтобы подклассы
Figure
можно было инстанцировать без оператора и каждый из них имел свойство optionalNew
равное true
. Для этого нужно написать метакласс для Figure:var FigureMeta = Function.subclass(
{
subclass: function () {
var constructor = Function.prototype.subclass.apply(this, arguments);
var result = function () {
var self = (
this instanceof arguments.callee
? this
: {__proto__: arguments.callee.prototype});
constructor.apply(self, arguments);
return self;
};
result.prototype = constructor.prototype;
result.__proto__ = this.__proto__;
return result;
},
optionalNew: true
});
Figure.__proto__ = FigureMeta.prototype;
Все подклассы
Figure
, определенные после установки метакласса, получат необходимые изменения.Конечно, метаклассы не стоит применять слишком широко, в частности,
FigureMeta
является совершенно надуманным примером. Однако в некоторых случаях они могут сделать код гораздо короче и понятнее. Например, при адаптировании в Akshell библиотеки js-forms, порта Django forms в JavaScript, метаклассы позволили создавать подклассы форм так же декларативно, как и в Django (там для этого тоже используются метаклассы, только питоновские). Код занимает всего лишь 30 строк.