Хочу представить вам функцию-конструктор классов 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; }
