Проблема в том, что в javascript нет привычного наследования. Максимум что можно сделать, это создать конструктор класса:
function Class(){/*тут инициализируем поля *}
А потом, через прототип, добавить методы, константы и статические переменные, которые будут одни на все экземпляры.
Class.prototype = {/*Методы*/}
Этого в большинстве случаев хватает, но иногда хочется большего.
А если очень хочется...
… то можно это реализовать. Классический способ выглядит так:
function inherit_A(Child, Parent) { var F = function () { }; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.super = Parent.prototype; }
И его даже можно использовать:
$(document).ready(function ()
{
function Man(name) { this.name = name }
Man.prototype.say = function () { console.log("My name is " + this.name) }
function Gentleman(name) { Man.call(this, name); }
inherit_A(Gentleman, Man);
});
У меня с ним, правда, есть одна маленькая проблема — обычно, для создания прототипа, я использую объектную нотацию, как было показано немного выше:
Class.prototype =
{
/*Методы*/
}
А, с этим наследованием, таким методом не очень попользуешься — или мои функции полностью затрут унаследованный прототип, или унаследованный прототип полностью затрет мои функции. К счастью, наследовать можно и по-другому.
Метод посвящается всем любящим объектные литералы
Вот и он:
function inherit_B(Child, Parent) { var F = function () { }; F.prototype = Parent.prototype; var f = new F(); for (var prop in Child.prototype) f[prop] = Child.prototype[prop]; Child.prototype = f; Child.prototype.super = Parent.prototype; }
Он просто берет, и копирует все свойства из прототипа родителя в прототип наследника, так что теперь мой любимый объектный литерал работает как надо:
$(document).ready(function () { function Man(name) { this.name = name } Man.prototype = { cosntructor: Man, THOUGHTS: "wanna beer!", say: function () { console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'") } } function Gentleman(name, prefered_beverage) { Man.call(this, name); this.prefered_beverage = prefered_beverage; } Gentleman.prototype = { cosntructor: Gentleman, THOUGHTS: "it's teatime!" } inherit_B(Gentleman, Man) function Programmer(name, prefered_lang) { Gentleman.call(this, name, "Cofee"); this.prefered_lang = prefered_lang; } Programmer.prototype = { cosntructor: Programmer, THOUGHTS: "runtime error 138? wanna debug XD!" } inherit_B(Programmer, Gentleman) var man = new Man("Jack"); var gentleman = new Gentleman("John", "Orange pekoe"); var programmer = new Programmer("James", "C++"); man.say(); gentleman.say(); programmer.say(); });
И консоль со мной согласна:
sample.js:11 My name is Jack and i think:'wanna beer!' sample.js:11 My name is John and i think:'it's teatime!' sample.js:11 My name is James and i think:'runtime error 138? wanna debug!'
Отлично. А теперь представим гипотетическую ситуацию, когда родительских классов много, и нужно, например, вызвать какой-нибудь метод, который был перегружен 2-3 класса назад. В такой ситуации, конечно, лучше еще раз хорошенько присмотреться к архитектуре своего приложения. Теперь допустим, что там все работает, как задумано, тогда чертовски неудобно писать, например, так:
this.super.super.super.someMethod.apply(this)
Но это тоже решаемо
function inherit_C(Child, Parent) { var F = function () { }; F.prototype = Parent.prototype; var f = new F(); for (var prop in Child.prototype) f[prop] = Child.prototype[prop]; Child.prototype = f; Child.prototype[Parent.prototype.__class_name] = Parent.prototype; }
Эта функция в прототип потомка добавляет объект с ссылкой на прототип родителя. Чтобы она заработала как надо, в прототип каждого класса нужно добавить поле
__class_name
Вот, например, предположим, что в нашей иерархии появился класс BadProgrammer, которому как раз понадобился удобный доступ к прототипу далекого предка. Немного модифицируем предыдущий пример:
$(document).ready(function () { function Man(name) { this.name = name } Man.pro THOUGHTS: "wanna beer!", say: function () { console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'") } } function Gentleman(name, prefered_beverage) { Man.call(this, name); this.prefered_beverage = prefered_beverage; } Gentleman.prototype = { __class_name: "Gentleman", cosntructor: Gentleman, THOUGHTS: "it's teatime!" } inherit_C(Gentleman, Man) function Programmer(name, prefered_lang) { Gentleman.call(this, name, "Cofee"); this.prefered_lang = prefered_lang; } Programmer.prototype = { __class_name: "Programmer", cosntructor: Programmer, THOUGHTS: "runtime error 138? wanna debug XD!" } inherit_C(Programmer, Gentleman) function BadProgrammer(name) { Programmer.call(this, name, "brainfuck"); } BadProgrammer.prototype = { __class_name: "BadProgrammer", cosntructor: BadProgrammer, THOUGHTS: "runtime error 138? wanna debug XD!", say: function () { this.THOUGHTS = this.Man.THOUGHTS; this.Man.say.apply(this); } } inherit_C(BadProgrammer, Programmer) var man = new Man("Jack"); var gentleman = new Gentleman("John", "Orange pekoe"); var programmer = new Programmer("James", "C++"); var badprogrammer = new BadProgrammer("Jake"); man.say(); gentleman.say(); programmer.say(); badprogrammer.say(); });totype = { __class_name: "Man", cosntructor: Man, THOUGHTS: "wanna beer!", say: function () { console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'") } } function Gentleman(name, prefered_beverage) { Man.call(this, name); this.prefered_beverage = prefered_beverage; } Gentleman.prototype = { __class_name: "Gentleman", cosntructor: Gentleman, THOUGHTS: "it's teatime!" } inherit_C(Gentleman, Man) function Programmer(name, prefered_lang) { Gentleman.call(this, name, "Cofee"); this.prefered_lang = prefered_lang; } Programmer.prototype = { __class_name: "Programmer", cosntructor: Programmer, THOUGHTS: "runtime error 138? wanna debug XD!" } inherit_C(Programmer, Gentleman) function BadProgrammer(name) { Programmer.call(this, name, "brainfuck"); } BadProgrammer.prototype = { __class_name: "BadProgrammer", cosntructor: BadProgrammer, THOUGHTS: "runtime error 138? wanna debug XD!", say: function () { this.THOUGHTS = this.Man.THOUGHTS; this.Man.say.apply(this); } } inherit_C(BadProgrammer, Programmer) var man = new Man("Jack"); var gentleman = new Gentleman("John", "Orange pekoe"); var programmer = new Programmer("James", "C++"); var badprogrammer = new BadProgrammer("Jake"); man.say(); gentleman.say(); programmer.say(); badprogrammer.say(); });
Класс BadProgrammer воспользовался мыслями самого первого звена нашей эволюционной цепочки классов, и, теперь, думает совсем не о том, о чём обычно думают программисты
My name is Jack and i think:'wanna beer!' My name is John and i think:'it's teatime!' My name is James and i think:'runtime error 138? wanna debug!' My name is Jake and i think:'wanna beer!'
И еще кое что
Не представляю в какиx случаях это может пригодится, но, возможно, когда-нибудь, кому-нибудь может понадобится множественное наследование. Его тоже вполне можно реализовать:
function inhertit_multiple(child) { for( var i = 1; i < arguments.length; ++i ) { var parent = arguments[i] for (var prop in parent.prototype) { if (!child.prototype[prop]) child.prototype[prop] = parent.prototype[prop]; } child.prototype[parent.prototype.__class_name] = parent.prototype; } }
Не очень-то и сильно отличается от предыдущей версии.
Для того, чтобы показать как оно работает, я придумал еще один вполне реалистичный пример:
$(document).ready(function () { function Mammy() { this.mammy_message = "You Dont love me!" } Mammy.prototype = { __class_name: "Mammy", say_something_wise: function () { console.log(this.mammy_message) } } function Daddy() { this.daddy_message = "I just don't want to be a dad!" } Daddy.prototype = { __class_name: "Daddy", say_something_wise: function () { console.log(this.daddy_message) } } function Lad() { this.lad_message = "And i want a candy!"; Mammy.apply(this); Daddy.apply(this); } Lad.prototype = { __class_name: "Lad", say_something_wise: function () { this.Daddy.say_something_wise.call(this); this.Mammy.say_something_wise.call(this); console.log(this.lad_message); } } inhertit_multiple(Lad, Mammy, Daddy) var lad = new Lad(); lad.say_something_wise(); });
Если это запустить, то в консоли появится примерно то что мы и ожидаем
I just don't want to be a dad! You Dont love me! And i want candy!