Как стать автором
Обновить

Комментарии 44

Как-то пытался написать свой велосипед с блекджеком и шлюхами поддержкой статических методов и полей (включая их наследование). В итоге сломал мозг и забил, хотя решения даже как-то заработало. Сейчас в сложных проектах использую решения фреймворков, в простых ограничиваюсь копированием прототипа.
Буквально вчера мы опубликовали на хабре ( mihteh.habrahabr.ru/blog/74697/ ) ссылку на нашу библиотеку _template.js, в которой мы реализовали свой способ описания ООП в js, согласно которому объекты создаются как экземпляры объекта — шаблона. Библиотека пока что представляет скорее академический интерес, нежели прикладной, но тем не менее. Подробное описание библиотеки в нашем блоге: blog.starcode.ru/js-obj-template/

Думаю это решение может быть уж если не полезным, то как минимум интересным :)
Почему некоторые примеры не подсвечиваются? и в примере 3 лишний тег в конце —
/code
Хм… похоже надо больше спать…
Прошу прощения за неудобство у всех кто успел наткнуться на неразобранную кашу в конце статьи. Дело в том что автор был уверен что редактирует черновик, пока не увидел эти комментарии. Каким образом автор нажал на кнопочку опубликовать, для самого автора остается загадкой. :)
Обещаю все исправить как можно скорее.
Ну вот, теперь статью можно нормально читать.
Спасибо, теперь буду дочитывать)
> автор был уверен что редактирует черновик, пока не увидел эти комментарии. Каким образом автор нажал на кнопочку опубликовать, для самого автора остается загадкой

Говорите о себе в третьем лице? =) Признак мании величия. Шутка ;)
пользуюсь классами от ExtJs. По вашей классификации это:
конструкторы / наследование прототипированием / свойства хранятся в прототипе / конструктор вызывается по цепочке суперклассов
Если Вы задумаете нечто серьезное, возникнет вопрос производительности и получите Вы один единственный вариант.
Мм… какой именно?
> Самая очевидная причина его популярности — волшебный синтаксис new Class(), столь милый сердцу, привыкшему к классическому ООП

Всё-таки «волшебный синтаксис» тут вторичен. Фабрика не создаёт и не связывает объект с прототипом. Довольно существенна разница по сравнению с вызовом/использованием конструктора.
Ну, я же не утверждал что оператор new — единственная разница. Я сказал что это основное преимущество в глазах многих.
Не утверждали, это правда, я про популярность. Конструктор создаёт объект (минус одна операция против фабрики), связывает прототип (минус ещё пару операций)… Вряд ли кого-то может сильно увлечь синтаксис привычный, на логику же смотрят. Очевидная причина популярности скорее привычное и компактное создание объектов-экземпляров, многократно описанное в офиц. доках именно таким образом. Думаю, так. ;-)
Люди, поймите наконец: «ООП != наследование + инкапсуляция + полиморфизм».
Да хватит уже эти фразы слащаво-шаблонные повторять, все всё понимают. Автор лишь анализирует и пытается классифицировать подходы — и в этом, разбирается в ООП в JS.
И что? Это всё есть в javascript. Напомню, javascript — это…

object-oriented
....object-based
........prototype-based
............delegation-based

… язык.
> function derivate(o) {
> function F() {}


Для оптимизации, функцию F в данном случае можно создать один раз и использовать повторно.

> Кажется что весь пример состоит из бесконечного повторения одних и тех же имен, одних и тех же многословных конструкций.

Да, для этих целей обычно выносят этот повторяющийся код по связке прототипов в обёртку (в простейшем случае — в обычную функцию), что повышает абстракцию (мы не завязаны на конкретные имена конструкторов) и улучшает реюз кода.

> И сталкиваемся с тем, что, во-первых, загрязняется наследование свойств (это не легко «ухватить», но свойства получаются из последнего прототипа в цепочке, вместо нужного), а во-вторых и в главных, отрабатывает совершенно не нужная на этом этапе и потенциально ресурсоемкая инициализация.

Да, верно. Но эти причины не столь критичны, как например, проверка входного параметра в родительском конструкторе. Возможны случаи, когда подобным образом вообще не отнаследоваться. Например:

function A(param) {
  if (!param) {
    throw 'Param required';
  }
  this.param = param;
}
A.prototype.x = 10;
 
var a = new A(20);
alert([a.x, a.param]); // 10, 20
 
function B() {}
B.prototype = new A(); // Ошибка

Поэтому, опять же, для этих целей использую обычно методику, предложенную Lasse Reichstein Nielsen с промежуточным пустым конструктором, который и свяжет прототипы. Объект для наследования создаётся именно от этого пустого конструктора, что позволяет избавится от недочётов, описанных выше.

> При всем обилии материалов по данному вопросу, я просто не смог найти в сети достаточно полного обобщающего анализа

Могу порекомендовать Тонкости ECMA-262-3. Часть 7. ООП.

В целом, спасибо за статью и анализ.
Согласен с вами по всем пунктам, но я старался не перегрузить примеры — информации и так слишком много.
Варианты доработки второго решения я планировал обсудить в продолжении.
Насчет ссылки — спасибо, обязательно посмотрю.
На бутылках с соком одно время такие бумажки встречались: «Вместо написанного на упаковке следует читать...»

Так вот, вместо «варианты доработки второго решения...» следует читать «варианты избавления от повторяющегося кода примера 7 я планирую обсудить в продолжении»
Имхо, статья вообще не о чем. Только запутывает. И примеры мне не понятные, вот почему в последнем случае нельзя делать так?

SubClass.prototype.getName = function() {
    return "SubSubClass(id = " + this.getId() + ") extends " + SubClass.call(this);
}

Знаете, просто тема сложная.
Насчет Вашего вопроса — попробуйте сами запустить то что получается. Лично я не понимаю, чего Вы хотите добиться с помощью SubClass.call(this) в данном контексте.
Сорри, опечатался
SubClass.prototype.getName = function() {
    return "SubSubClass(id = " + this.getId() + ") extends " + SubClass.prototype.getName.call(this);
}

Вот, теперь гораздо понятней )

Да, Вы правы, так тоже можно, то при этом Вы прямо обращаетесь по имени к родительскому классу, чего хотелось избежать. В предыдущем примере (6-ом) избежать этого можно было с помощью локальной переменной. Я как раз и хотел показать что в данном случае похожий прием оказывается гораздо менее удобным.

(Кстати, спасибо что обратили внимание на этот пример, я исправил там опечатку)
Ах, нет. Перечитал Ваш код. Конечно так нельзя. Вы же внутри SubClass.prototype.getName вызываете SubClass.prototype.getName. Как Вы думаете, что получится?
Черт, конечно же я имел ввиду родительский класс. Там в начале было SubSubClass
Ок, тогда см. предыдущий камент )
Тема как раз таки не сложная. Просто ООП в каноническом понимании слабо применим к JS. Не нужно пытаться всю логику приложения положить на «классы» с наследованием — этот подход идет вразрез с идеологией JS.
Идея как раз и была в том, чтобы проанализировать те самые решения, идущие вразрез с идеологией.
А в чем скрытый смысл подобного анализа? Убедиться, что эти решения плохо работают? :)
Смысл этого, как и любого анализа в том чтобы понять )
чтобы понять, практикуйте Scheme, например Gambit, PLT или Bigloo — все станет настолько очевидным, что про классы и канонический ООП вы забудете очень быстро.
> Убедиться, что эти решения плохо работают? :)

А какие решения? И почему плохо? Связываются прототипы, тем самым наследуются. Что здесь не так? Это заложено в идеологию ES. Опять же, чтобы увидеть объектно-ориентированную суть и показать наследование, можно даже не писать какой-то сложный код с конструкторами, иерархией и т.д. Достаточно простейшего примера:

alert(1..toString()); // "1"

Пример показывает, что ES изначально пронизан наследованием и это — в его идеологии. Чтобы поддержать эту идеологию и реализовать её в своих объектах, нужно связать прототипы. Различные решения это и предлагают. Почему же они «плохо работают»?
> Не нужно пытаться всю логику приложения положить на «классы» с наследованием — этот подход идет вразрез с идеологией JS

А почему вразрез? Если цепь прототипов неизменна, то она вполне подходит по цепь наследования. Более того, как я отмечал, разница «класс vs. прототип» в определённых случаях может быть вообще не важна.
Такие вещи явно надо читать с утра… вынос мозга =)
ой какой хороший труд. ну наконец то появился материал на русском куда я буду отправлять умнеть вопрошаек
Как-то тяжело примеры Ваши воспринимаютя. Да и классификация сразу по трем «вопросам» в разных сочетаниях запутыват. Есть же хорошо устоявшиеся паттерны, со своими названиями: вот здесь, например.
Автор расмотрел Javascript с терминологией прототипных яызков програмирования.
en.wikipedia.org/wiki/Prototype-based_programming
И применённая им терминология вполне умесна.
Возможно стоит расширить кругозор? Мне например интересно само направление, узнал что-то новое.
Я и не утверждал, что терминология неуместна. Она (терминология) не позволяет в голове выстроить четкой структуры мыслей автора.
Я читал этот текст. На мой взгляд он не очень удачен. Названия звучат замечательно: прототипное, на замыканиях, ссылочное, связанное и ленивое.
Вот только, то что авторы назвали ссылочным наследованием — всего лишь одна из множества надстроек над тем что они называли прототипным наследованием (при этом, все без исключения паттерны используют прототипы, почему только один из них «прототипный»? ). То что они назвали связанным наследованием — еще одна надстройка из того же ряда и опять-таки, я не понял, почему оно «связанное». Ленивое наследование это вообще, фактически ссылка на целый фреймворк, который, как они сами говорят: «uses synchronous xmlhttp which really, really is a bad thing to the browser UI». Супер.

Что касается примеров — вероятно Вы правы. Но уж как получилось )
А Вам не кажется, что любой механизм наследования, реализованный с помощью Javascript, всегда будет, в той или иной мере, основываться на прототипировании? Прототипирование является более гибким подходом к работе с объектами и позволяет решать задачи непосильные для «классического» наследования.
Честно говоря не очень понял что вы хотите сказать этим вопросом. Допустим, все механизмы основаны на прототипах — и что? называть только один из-них прототипным будет правильно? Допустим прототипное наследование лучше классического — и что? Предлагать неюзабельный фреймворк (реализующий классическое наследование, кстати) в качестве паттерна станет нормальным?
Как раз недавно читал миниобзор на эту тему: phrogz.net/JS/Classes/OOPinJS2.html
Понравилось решение, которое там предлагают — расширить объект Function:
Function.prototype.inheritsFrom = function( parentClassOrObject ){ 
	if ( parentClassOrObject.constructor == Function ) 
	{ 
		//Normal Inheritance 
		this.prototype = new parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject.prototype;
	} 
	else 
	{ 
		//Pure Virtual Inheritance 
		this.prototype = parentClassOrObject;
		this.prototype.constructor = this;
		this.prototype.parent = parentClassOrObject;
	} 
	return this;
} 


И тогда можно делать так:
// Абстрактный базовый класс (по крайней мере, как его называет автор:))
LivingThing = { 
	beBorn : function(){ 
		this.alive = true;
	} 
} 

// Класс, который, с одной стороны, обращается к методам родительского
// А с другой стороны, создаёт объекты, не зная конкретного своего наследника
function Mammal(name){ 
	this.name=name;
	this.offspring=[];
} 
Mammal.inheritsFrom( LivingThing );
Mammal.prototype.haveABaby=function(){ 
	this.parent.beBorn.call(this);
	var newBaby = new this.constructor( "Baby " + this.name );
	this.offspring.push(newBaby);
	return newBaby;
} 

//
function Cat( name ){ 
	this.name=name;
} 
Cat.inheritsFrom( Mammal );
Cat.prototype.haveABaby=function(){ 
	var theKitten = this.parent.haveABaby.call(this);
	alert("mew!");
	return theKitten;
} 



То есть после этого в коде удобно ссылаться на родительские методы.
Да, действительно, это один из способов «очеловечить» код моего 7-го примера. По сути, это все тот же вариант конструкторы + свойства в прототипе + наследование через прототипы.
Однако это решение не идеально — как минимум оно не избавляет от лишней инициализации.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории