Статьи

Наследование Javascript

Поиск в Google «Javascript Inheritance» возвращает некоторые интересные, но, IMO, также разочаровывающие результаты, такие как различные взгляды Дугласа Крокфорда на Классическое наследование в JavaScript или подход Кевина Линдси , первый не дает четкого ответа, в то время как последний полагается на соглашение о присоединении свойство «суперкласса» для ваших объектов и фактически требует склеивания ваших объектов для каждого создаваемого вами экземпляра.

Тем временем такие библиотеки, как prototype и MochKit , разработали свои собственные соглашения и стратегии, которые вы можете или не можете оценить. Я сварливый в каждом случае. Прототип, несмотря на то, что в нем есть какой-то очень классный код, расширяет встроенные типы Javascript, такие как Object, которые я не ценю, заставляет меня нервничать по поводу смешивания в других библиотеках Javascript из других источников. В то же время, глядя на MochKit (который может быть лучшей реализацией AJAX, сделав хорошую ссылку на витую ), страдает тот тип ловкости, который здесь представлен , когда речь идет о его «Base».

Альтернатива

Есть другой способ, который, вероятно, дает вам большую часть того, что вы ожидаете, если вы пришли от языка, такого как Java ( кажется, не может его остановить;)). Это благодаря Троэлсу Кнаку-Нильсену (автору indite — проверяющего виджета wysiwyg в реальном времени), который давно рассказал мне об этом в списке рассылки JPSpan здесь .

function copyPrototype(descendant, parent) { var sConstructor = parent.toString(); var aMatch = sConstructor.match( /s*function (.*)(/ ); if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; } for (var m in parent.prototype) { descendant.prototype[m] = parent.prototype[m]; } }; 

Примечание: приведенный выше код обновляется после первоначальной публикации этого комментария

На первый взгляд, с этим регулярным выражением это выглядит как хак, но если вы позволите ему погрузиться (что заняло у меня больше года), вы поймете, что это не так. Стоит помнить, что Троэльс сказал об этом;

Прежде всего — я полностью понимаю и согласен с тем, чтобы не расширять функциональность языка. Тем не менее, я думаю, что смысл в отношении javascript и наследования часто неправильно понимают. Для javascript центральным является то, что это «типизированный, беспринципный язык». Это относится и к ECMAscript3.0. Люди чаще всего ошибаются, когда ошибочно принимают прототипы за уроки, но это не так.

В типизированном ОО-языке код объектов лежит на классе — объекты являются лишь экземплярами. В js код лежит в объекте. Следовательно, объект не привязан к прототипу. В js можно манипулировать средой выполнения прототипа даже после создания объектов. Попробуйте сделать это с типизированным языком.

Тем не мение. В последнее время я много работал с js и обнаружил, что одноуровневое наследование достаточно просто с использованием прототипов, но дальнейшее наследование вызывает проблемы. Я думаю, вы пришли к тому же выводу с JPSpan. Мое первое приближение к этой теме вызвало много странных хаков, которые я не ценю. В конце концов я изобрел решение, которым я пользуюсь, и которым я очень доволен. Это может выглядеть как хак, но это работает так хорошо, что я как бы прощаю это за это.

Чтобы защитить себя дальше, я укажу, что это не расширение языка, если вы просто понимаете, что оно делает. Это просто утилита, расширяющая прототип — которую не следует путать с наследованием классов (хотя результат тот же).

Легче всего увидеть точку Троэльса, попробовав это …

Основное Наследование

То, как вы ожидаете …

function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { // Call the parent constructor to setup // the parent object properties... this.Animal(); }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); var d = new Dog(); d.category();
function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { // Call the parent constructor to setup // the parent object properties... this.Animal(); }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); var d = new Dog(); d.category(); 

… и я получаю предупреждение «животное»

Еще один поворот, на этот раз без вызова родительского конструктора …

function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { // No call to parent constructor this.species = "canine"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); var d = new Dog(); d.category();
function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { // No call to parent constructor this.species = "canine"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); var d = new Dog(); d.category(); 

На этот раз предупреждение гласит «клык».

Много поколений

Часто проблема с различными подходами к наследованию Javascript, вот как подход Troels решает эту проблему;

function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { this.Animal(); this.species += ":dog"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); function Poodle() { this.Dog(); this.species += ":poodle"; }; // Poodle "inherits" from Dog copyPrototype(Poodle, Dog); var p = new Poodle(); p.category();
function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { this.Animal(); this.species += ":dog"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); function Poodle() { this.Dog(); this.species += ":poodle"; }; // Poodle "inherits" from Dog copyPrototype(Poodle, Dog); var p = new Poodle(); p.category(); 

… Оповещения: «животное: собака: пудель»

Переопределение родительских методов

По умолчанию этот подход просто заменяет методы. Это означает, что вам необходимо убедиться, что вы copyPrototype перед назначением любых методов прототипу подкласса, в противном случае родительские методы переопределят дочерние методы;

function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { this.Animal(); this.species += ":canine"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); // Override parent method _after_ calling copyPrototype Dog.prototype.category = function() { alert(this.species.toUpperCase()) };
function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Dog() { this.Animal(); this.species += ":canine"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); // Override parent method _after_ calling copyPrototype Dog.prototype.category = function() { alert(this.species.toUpperCase()) }; 

… Отображает «ЖИВОТНОЕ: Клык»

Также возможно, хотя и немного болезненно, вызвать родительский метод, который был переопределен, с помощью apply . Изменение вышеуказанного метода Dog.prototype.category демонстрирует это;

Dog.prototype.category = function() { // Call overridden parent method... Animal.prototype.category.apply(this); alert(this.species.toUpperCase()) };
Dog.prototype.category = function() { // Call overridden parent method... Animal.prototype.category.apply(this); alert(this.species.toUpperCase()) }; 

… что приводит к двум предупреждениям. Если вам нужно передать параметры метода, вам нужно будет передать массив arguments как второй аргумент метода apply ().

Примеси

MixIns (в основном множественное наследование) также работает довольно предсказуемо;

function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Quadruped() {}; Quadruped.prototype.run = function() { alert('Running...'); } function Dog() { this.Animal(); this.species += ":canine"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); // Dog "inherits" from Quadruped copyPrototype(Dog, Quadruped); var d = new Dog(); d.category(); d.run();
function Animal() { this.species = "animal"; }; Animal.prototype.category = function() { alert(this.species); }; function Quadruped() {}; Quadruped.prototype.run = function() { alert('Running...'); } function Dog() { this.Animal(); this.species += ":canine"; }; // Dog "inherits" from Animal copyPrototype(Dog, Animal); // Dog "inherits" from Quadruped copyPrototype(Dog, Quadruped); var d = new Dog(); d.category(); d.run(); 

… Два предупреждения: «животное: клык» и «бег …»

Предостережения

Два конкретных предупреждения с этим подходом …

1. При вызове copyPrototype имеет значение, как уже упоминалось. На практике это означает, что лучше всего вызывать его сразу после объявления дочернего конструктора.

2. Не объявляйте метод toString для родительского объекта Function класса, так как это может нарушить регулярное выражение в copyPrototype . Вы все еще можете изменить родительский прототип, не причиняя никакого вреда. Другими словами это сломает copyPrototype ;

Animal.toString = function() { return "this is an animal"; }
Animal.toString = function() { return "this is an animal"; } 

Но это нормально;

Animal.prototype.toString = function() { return "this is an animal"; }
Animal.prototype.toString = function() { return "this is an animal"; } 

В итоге

Из всех подходов, которые я видел, для меня это стало самым убедительным — лучший способ сохранить ваш код СУХИМ без необходимости изменять основные типы Javascript и вводить странные побочные эффекты. Это также всего 8 строк кода, так что стоит недорого. Благодарю Troels за возвышенное латеральное мышление.