MyTetra Share
Делитесь знаниями!
Javascript - наследование для чайников
Время создания: 02.11.2011 22:19
Раздел: Компьютер - Программирование - Java Script - Классы в JavaScript
Запись: xintrea/mytetra_syncro/master/base/1320261567w5g2ed3rj5/text.html на raw.github.com

Прочитав очередную умную книжку про javascript, стал разбираться в методах реализации в нём наследования. Ну, то есть всем конечно понятно, что в реальном, большом, проекте лучше всего для этого использовать функцию из какой-нибудь библиотеки( благо их много ), но ведь хочется понять, как это вообще работает.

Решил посмотреть, что на эту тему есть на хабре — в общем, лучше бы я этого не делал — кто-то пишет статьи про свои костыли на пару страниц потому что библиотечная функция чем-то не понравилась, кому-то кажется удобным доступ к предку по порядковому номеру. в общем много веселья, много кода, споров тоже достаточно а самой базы просто нет. И мне показалось что неплохо бы это исправить.

1. Итак, а в чем собственно проблема?

Проблема в том, что в javascript нет привычного наследования. Максимум что можно сделать, это создать конструктор класса:

function Class(){/*тут инициализируем поля *}

А потом, через прототип, добавить методы, константы и статические переменные, которые будут одни на все экземпляры.

Class.prototype = {/*Методы*/}

Этого в большинстве случаев хватает, но иногда хочется большего.

2. А если очень хочется...

… то можно это реализовать. Классический способ выглядит так:

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 Men(name) { this.name = name }

Men.prototype.say = function () { console.log("My name is " + this.name) }

function Gentleman(name) { Men.call(this, name); }

inherit_A(Gentleman, Men);

});

У меня с ним, правда, есть одна маленькая проблема — обычно, для создания прототипа, я использую объектную нотацию, как было показано немного выше:

Class.prototype =

{

/*Методы*/

}

А, с этим наследованием, таким методом не очень попользуешься — или мои функции полностью затрут унаследованный прототип, или унаследованный прототип полностью затрет мои функции. К счастью, наследовать можно и по-другому.

3. Метод посвящается всем любящим объектные литералы

Вот и он:

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 Men(name) { this.name = name }

Men.prototype =

{

cosntructor: Men,

THOUGHTS: "wanna beer!",

say: function ()

{

console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'")

}

}

function Gentleman(name, prefered_beverage)

{

Men.call(this, name);

this.prefered_beverage = prefered_beverage;

}

Gentleman.prototype = { cosntructor: Gentleman, THOUGHTS: "it's teatime!" }

inherit_B(Gentleman, Men)

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 men = new Men("Jack");

var gentleman = new Gentleman("John", "Orange pekoe");

var programmer = new Programmer("James", "C++");

men.say();

gentleman.say();

programmer.say();

console.log("");

});

И консоль со мной согласна:

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)

4. Но это тоже решаемо

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 Men(name) { this.name = name }

Men.prototype =

{

__class_name: "Men",

cosntructor: Men,

THOUGHTS: "wanna beer!",

say: function ()

{

console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'")

}

}

function Gentleman(name, prefered_beverage)

{

Men.call(this, name);

this.prefered_beverage = prefered_beverage;

}

Gentleman.prototype =

{

__class_name: "Gentleman",

cosntructor: Gentleman,

THOUGHTS: "it's teatime!"

}

inherit_C(Gentleman, Men)

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: Programmer,

THOUGHTS: "runtime error 138? wanna debug XD!",

say: function () { this.THOUGHTS = this.Men.THOUGHTS; this.Men.say.apply(this); }

}

inherit_C(BadProgrammer, Programmer)

var men = new Men("Jack");

var gentleman = new Gentleman("John", "Orange pekoe");

var programmer = new Programmer("James", "C++");

var badprogrammer = new BadProgrammer("Jake");

men.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!'

5. И еще кое что

Не представляю в каки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!

Вот так все это и работает. Весь код и примеры положил сюда — вдруг пригодится.

UPD. Как справедливо заметил один хороший человек, во всех методах, кроме первого, instanceof отказывается признавать класс экземпляром родительского класса. При множественном наследовании этот недостаток сохранился, но для остальных случаев я его устранил.

 
MyTetra Share v.0.65
Яндекс индекс цитирования