Хочу представить вам функцию-конструктор классов createClass.
Чем хорош этот конструктор:
1. Сам код выглядит более читабельным и понятным
2. Поддержка множественного наследования
3. Поддержка абстрактных методов
4. Наследование статических методов и свойств класса
5. Умный метод instanceOf (проверяет по всей цепочке классов)
Итак, сразу пример:
Все свойства класса должны определяться в конструкторе (статические свойства в статическом конструкторе, о нём речь пойдёт ниже).
Класс Animal имеет обстрактый метод “say”. Классы Kitty и Doggy наследуется от Animal и реализуют метод “say”. При этом, поскольку в Kitty конструктор явно не задан, он заимствуется у Animal. В Doggy конструктор задан явно, поэтому родительский конструктор нужно вызвать тоже явно Animal.call(this, name). Метод eat родительского класса можно вызвать как Animal.fn.eat.call(this) это тоже самое, что Animal.prototype.eat.call(this)
Дальше:
Singleton имеет статическое свойство _instance и статический метод getInstance. Этот класс может применяться в качестве реализации паттерна Singleton. SuperDoggy наследуется от двух классов Animal, Singleton и реализует метод “say”.
Стоит отдельно отметить метод construct в обьекте statics. Этот метод вызывается при создании самого класса и при создании класса-наследника. Он позволяет корректно копировать при наследовании статические свойства объекта. Если в объекте prototype есть метод constructor, который создаёт объект, то почему бы не иметь constructor, создающий сам класс?
Примеры применения метода “instanceOf”:
Изначально идея была взята из книги Девида Фленагана JavaScript 5 издание: пример из книги. В итоге этот код эволюционировал в представленный в статье.
Поскольку исходник не так велик, публикую его прямо тут:
Чем хорош этот конструктор:
1. Сам код выглядит более читабельным и понятным
2. Поддержка множественного наследования
3. Поддержка абстрактных методов
4. Наследование статических методов и свойств класса
5. Умный метод instanceOf (проверяет по всей цепочке классов)
Итак, сразу пример:
var Animal = createClass({
abstracts: ['say'],
construct: function(name) {
this.name = name;
},
eat: function() {
return 'ou yeah...';
}
});
var Kitty = createClass({
extend: Animal,
say: function() {
return this.name + ' said: RrrrRRR...Mau..';
},
eat: function() {
return Animal.fn.eat.call(this) + 'FISH!!'
}
});
var Doggy = createClass({
extend: Animal,
construct: function(name) {
Animal.call(this, name);
console.log('doggy have been created');
},
say: function() {
return this.name + ' said: GAV!';
},
eat: function() {
return Animal.fn.eat.call(this) + 'MEAT!!'
}
});
var sharik = new Doggy('sharik'); // doggy have been created
var murka = new Kitty('murka');
console.log( sharik.eat(), sharik.say()); // ou yeah...MEAT!! sharik said: GAV!
console.log( murka.eat(), murka.say()); // ou yeah...FISH!! murka said: RrrrRRR...Mau..
Все свойства класса должны определяться в конструкторе (статические свойства в статическом конструкторе, о нём речь пойдёт ниже).
Класс Animal имеет обстрактый метод “say”. Классы Kitty и Doggy наследуется от Animal и реализуют метод “say”. При этом, поскольку в Kitty конструктор явно не задан, он заимствуется у Animal. В Doggy конструктор задан явно, поэтому родительский конструктор нужно вызвать тоже явно Animal.call(this, name). Метод eat родительского класса можно вызвать как Animal.fn.eat.call(this) это тоже самое, что Animal.prototype.eat.call(this)
Дальше:
var Singleton = createClass({
statics: {
construct: function() {
this._instance = null;
},
getInstance: function() {
if(this._instance === null) {
this._instance = new this();
}
return this._instance;
}
}
});
var SuperDoggy = createClass({
extend: [Animal, Singleton],
say: function() {
return this.name + ' said: GAV! GAV! GAV!';
}
});
var dog1 = SuperDoggy.getInstance(),
dog2 = SuperDoggy.getInstance();
dog1.name = 'Bob';
console.log(dog1 === dog2); // true
console.log(dog1.eat(), dog1.say()); // ou yeah... Bob said: GAV! GAV! GAV!
Singleton имеет статическое свойство _instance и статический метод getInstance. Этот класс может применяться в качестве реализации паттерна Singleton. SuperDoggy наследуется от двух классов Animal, Singleton и реализует метод “say”.
Стоит отдельно отметить метод construct в обьекте statics. Этот метод вызывается при создании самого класса и при создании класса-наследника. Он позволяет корректно копировать при наследовании статические свойства объекта. Если в объекте prototype есть метод constructor, который создаёт объект, то почему бы не иметь constructor, создающий сам класс?
Примеры применения метода “instanceOf”:
console.log( dog2.instanceOf(Kitty) === false ); // true
console.log( dog2.instanceOf(SuperDoggy) === true ); // true
console.log( dog2.instanceOf(Animal) === true ); // true
console.log( sharik.instanceOf(Animal, Singleton) === false ); // true
console.log( dog2.instanceOf(Animal, Singleton) === true ); // true
Изначально идея была взята из книги Девида Фленагана JavaScript 5 издание: пример из книги. В итоге этот код эволюционировал в представленный в статье.
Поскольку исходник не так велик, публикую его прямо тут:
function createClass(data)
{
var abstracts = data.abstracts || [],
statics = data.statics || {},
extend = data.extend || [];
if(!(extend instanceof Array))
extend = [extend];
// define constructor
var constructor;
if(data.construct) {
constructor = data.construct;
} else if(extend.length) {
constructor = function() {
for(var i=0; i<extend.length; i++) {
extend[i].apply(this, arguments);
}
}
} else {
constructor = function() {};
}
// prototype for our class.
var proto = {};
delete data.construct;
delete data.abstracts;
delete data.statics;
delete data.extend;
// borrow methods from parent classes
for(var i=0; i<extend.length; i++) {
var parent = extend[i];
// static constructor
if( typeof parent.construct == "function")
parent.construct.call(constructor);
// copy static methods
for(var p in parent) {
if (typeof parent[p] != "function" || p == "construct") // copy only functions
continue;
constructor[p] = parent[p];
}
// Copy prototype methods
for(var p in parent.prototype) {
if (typeof parent.prototype[p] != "function" || p == "constructor")
continue;
proto[p] = parent.prototype[p];
}
}
// build abstract static methods
if(statics.abstracts) {
for(var p=0; p<statics.abstracts.length; p++) {
proto[ statics.abstracts[p] ] = function() {
throw p + ' is static abstract method'
};
}
}
// build abstract prototype methods
for(var p=0; p<abstracts.length; p++) {
proto[ abstracts[p] ] = function() {
throw p + ' is abstract method'
};
}
// internal methods
proto.instanceOf = function(_class) {
if(arguments.length > 1) {
var res = true;
for(var i=0; i<arguments.length; i++)
res = res && this.instanceOf(arguments[i]);
return res;
}
if(constructor === _class)
return true;
for(var i=0; i<extend.length; i++) {
if( extend[i].prototype.instanceOf.call(this, _class) )
return true;
}
return _class === Object;
};
// rest of data are prototype methods
for(var p in data) {
if (typeof data[p] != "function") // copy only functions
continue;
proto[p] = data[p];
}
// static functions of class
for(var p in statics) {
if (typeof statics[p] != "function") // copy only functions
continue;
constructor[p] = statics[p];
}
// static constructor
if( typeof statics.construct == "function")
statics.construct.call(constructor);
// proto.constructor = constructor;
constructor.prototype = proto;
constructor.fn = proto; // short case
// Finally, return the constructor function
return constructor;
}