Comments 97
а почему бы не делать вот так:

function Test(){}

Test.prototype = {
x: 5,
y: 3,
sum: function(){
return this.x + this.y;
}
};
Так Вы потеряете то, что уже было в этом прототипе. Т.е. исключаете возможность "наследования" (да простят мне это слово, не знаю как правильно сказать :)
да, но разговор шел (если я еще не потерял суть) не о наследовании в статье, а о определении класса. Так что затереть то, что было в прототипе, в этом случае - вполне допустимо.

имхо, все таки, лучше, нежели каждый раз повторять слово prototype :)

...или я опять в чем-то не прав?
Спасибо за столько полный разбор создания классов в JavaScript, а то javascript очень активно сопротивляется ООП методики.
Может стоит с ним по ласковее и не будет сопротивляться? Если натягивать классориентированный подход на прототипоориентированный язык, понятно, что ему это не понравится.
Используйте библиотеку prototype.js там есть работа с классами и поудобнее, если и наследование классов и еще много чего хорошего.
Не делайте этого! 40 набла это ошибка, в топике на форуме объясняется почему!

Цитата: Дмитрий Котеров
Знаете, что я для себя отметил, прочитав (правда, бегло) весь этот топик... А вот что. Когда понадобится собеседовать человека на должность классного JavaScript-программиста, основное требование к нему будет - прекрасно разбираться во всей описанной выше кухне. Не больше и не меньше. Потому что сам я в ней, похоже, разбираюсь недостаточно хорошо.
чувствую в очередной раз себя отсталым от жизни. пошел искать в гугле javascript inheritance...
Там нет работы с классами. Там есть эмуляция некого подобия классов средствами прототипного языка. Для людей не знакомым с прототипами, несомненно, удобнее использовать их, но если понять, как работают прототипы, то намного легче и приятнее работать с ними.
Конечно эмуляция, что поделать, если JS практически не умеет общаться с классами. Библиотека нужна для ускоренной разработки, иногда лучше ее взять, чем мудрить свое...
Так он не умеет общаться с классами ни практически, ни теоретически. У него другая парадигма, которую почему-то считают более убогой или просто урезанной от "КЛАССической". Но она просто другая. И в контексте применения JS она гораздо лучше подходит.
Да что я буду спорить с вами на пустом месте. Возмите библиотеку и посмотрите она умеет делать работу с классами и не суть эмуляция это или нет и что в Js нет работы с классами. Вы хоть посмотрите сначала.
Я знаком с ней. Досконально. И то, что она делает, это не классы.
Что же вы всетаки понимаете под работой с классами?
Если работа с объектами, то да, JS - "объектно ориентированный", но умеет ли он наследывать классы, переписывать свойства, определять видимость и т.д.? Еще древний clipper был объектно-ориентированным в понятии JS.

Я не специалист в JS, но помоему это не объектно-ориентированный язык, в отличие от Java.

Приведите пример объявления класса, и унаследуйте от него другой.

Насколько я понимаю прототипы, это, грубо говоря "шаблоны", применяемые больше при оптимизации кода, также с их помошью код делаеться более человеко понимаемым.
Просто, смотря кого считать разработчиками. Если каждого вебмастера, который пытается добавить на свою страницу какой-то эффект, то да, их тьма и им понимать ничего не нужно. Но они и без классов прекрасно обходятся.
Настоящие разработчики, все-таки, начинают постепенно появляться в должных количествах.
Мне кажется что, если-бы объектно - прототипная парадигма лучше подходила, то в ECMA 4 не стали бы добавлять классы.
Прототипная парадигма больше подходит к одному, классовая к другому.
Для работы в браузерах больше подходит прототипная.
ECMA 4 же разрабатывается уже не столько для браузеров, сколько для более широкого круга задач.
К сожалению тема приватных свойств не раскрыта. Да и с посмотреть профиль DEL полностью согласен, не стоит изобретать велосипед, когда есть мотоцикл. Хотя для общего развития и полезно.
Если вы про эмуляцию классов, то, имхо, мотоцикл, это как раз прототипы. Только, конечно, с велосипеда на мотоцикл так просто не пересесть, придется долго тренироваться. А эмуляция классов, это вообще даже не велосипед, а самые что ни на есть костыли.
Это я про попытки самостоятельно реализовать то, что уже реализовано. На самом деле разбираться с тем, как это на самом деле работает нужно не каждому, большинству и prototype хватит. В свое время пришлось очень сильно голову ломать, параллельно с упомянутым мною топиком в форуме Котерова. Я имею ввиду взаимодействие тройки объекта, прототипа и конструктора.
В результате в каждом объекте создаётся свой, отдельный метод sum.

Не нужно считать разработчиков js-движков ламерами. Это достаточно частый случай и оптимизирован он очень хорошо.
Так же я думаю вы понимаете, что разбор исходного кода происходит только один раз и байт-код (или как лучше назвать) соответствующий sum будет только в одном экземпляре.
Вы знаете, когда отлаживаешь проект с большим количеством javascript под популярными браузерами, складывается ощущение, что "разработчики js-движков" не только ламеры, но и вообще безответственные лентяи. В примитивных случаях все ок, но как только кода становится много - память начинает течь, причем под разными браузерами в разных местах. И для борьбы с этим приходится выдумывать разные идиотские хаки. Так что не стоит рассчитывать на всякую "оптимизацию" - ее там почти что нет.
Приведите пожалуйста примеры с утечками памяти, кроме классических анонимных обработчиков в IE.
Лучше обратиться с таким вопросом к посмотреть профиль Александру Шуркаеву - он куда профессиональнее меня в области javascript (и в проектах, о которых я упоминал, боролся с утечками именно он).
В результате в каждом объекте создаётся свой, отдельный метод sum.

Как же нет, когда вы сами в этой статье доказываете обратное?
Прошу прощения, ctrl-c не сработал. Цитата следующая:
Так, уже многое написано о проблеме наследования (котого в JS нет).
Автор, узнай, что такое функциональное программирование.
Опечатка:

"К счастью, JS — это не Цэ-два-креста, и у нас есть замечательное предложение this."

with, а не this
"Почему не работает — загадка. Как ни крути, а слово prototype придётся повторять"

не работает из-за неопределённости - то ли выполнять это всё в контексте with, то ли как самостоятельные присваивания. В каком-то языке (в VBScript что ли?) видел замечательную вещь - начальная точка означает, что работаем с with. т.е. было бы что-то такое:

with(Test.prototype)
{
.x=5
.y=3
}
у нас есть замечательное предложение this (или как поправили выше with)

Оно далеко не такое замечательное. Применять его не рекомендуют уже даже разработчики JS.
with не позволяет нормально оптимизировать код и может иметь нежелательные эффекты, когда использующий не совсем четко представляет, как разрешается область видимости.
А мне наоборот нравится первый способ где всё в одном замыкании. Всё красиво и никаких бесконечных prototype, constructor, this и т.д.
Вы невнимательны.
Так, уже многое написано о проблеме наследования (котого в JS нет). Я же постараюсь рассказать о менее освещённом, но едва ли не более важном подводном камне: грамотной реализации методов.
Да, конечно.

Прежде всего: способ заточен именно под эмуляцию наследования (см. заглавие статьи). Как я уже писал, осветить это моей целью не было. (Кстати, метод inherits поражает своей очевидной наглядностью и безусловно должен быть рекомендован начинающим JS-программистам, не правда ли?)

Что касается method в отрыве от наследования — ценности он не имеет.
Я прекрасно знаю, под что заточен способ.

Метод inherits, конечно, далеко не мёд. Хотя, по сравнению с Y-комбинатором, который тоже где-то есть у Крокфорда, inherits - просто идеал ;) Но на деле, Крокфорд предлагает совсем другой подход к наследованию - через прототипы.

method в отрыве от наследования вполне себе работает.

Я лично предпочитаю такой подход к объектам:

var ServiceLocator = {
locate: function (service_name) {
// function body
},
}

Ну и стараюсь использовать наследование через прототипы.

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

Впрочем, пустое. Удачи, пишите ещё.
Вы уверены, что это наследование, а не просто создание экземпляра класса? Не путаете ли вы, а может я понятия?
Я вообще не вижу смысла в использовании method.

Test.method('sum',function(){return x+y;});

и

Test.prototype.sum=function(){return x+y;}

почти равнозначны. Почти — потому что method работает менее эффективно.
Каждому своё. Вы не видите, кто-то увидит. Создатели большинства библиотек вообще используют свои конструкции extend, которые тем не менее, довольно похожи местами.

Насчёт эффективности - не знаю.
Этот товарищ не понимает значения словосочетания "syntactic sugar", если вообще читал ту статью Крокфорда.
Про меньшую эффективность - это полнейший абсурд.

Function.prototype.method = function (name, func) {
this.prototype[name] = func;
return this;
};

По коду выше прекрасно видно, что эти конструкции совершенно равнозначны. Единственное отличие - возможность chaining'а при таком объявлении методов, что иногда бывает удобно.
А где там особый сахар-то? Подсластитель-ксилит на любителя. А если говорить об эффективности и равнозначности, то обратите внимание, что Test.method() - это всё-таки вызов функции, а значит предстоит вычисление аргументов, вычисление this, формирование scope chain и т.д., да и саму функцию (Function.prototype.method) нужно предварительно создать.
На любителя, да. Но тем не менее. Орать по поводу его неэффективности - это смешно.

Если создание дополнительной функции так уж ресурсоёмко, давайте, может, вообще откажемся от абстракций в JS? Будем всё писать как в старые добрые времена, 5 лет назад :)
Честно говоря, не заметил, чтобы кто-то здесь орал. Тихо ответили, что предложенный вариант не НЕэффективен, а МЕНЕЕ эффективен. Mожете называть это смешным и абсурдным, но это так и есть (во всяком случае это моё такое имхо), или же докажите обратное вместо того, чтобы говорить о совершенной однозначности. Каждый пук чего-то стоит, и мы обязаны знать об этом хотя бы в теории.

В старые добрые времена писать тоже умели. В новые времена полно ужасающего кода (ака классы-в-массы, чем-сложней-тем-умней). ;-)
Чтобы поставить в этом маловажном вопросе точку, я просто спросил у Дугласа его мнение по этому поводу. Он ответил: да, потеря эффективности есть. Незначительная. Но есть.
Если мы создадим сто объектов Test — получим где-то в памяти сто функций sum.
Это, вообще говоря, не очевидно. Думаю даже, что это не так. Такое объявление функции в жабьем скрипте не является замыканием вроде бы, поэтому есть подозрение, что сто функций всё-таки не создаётся.
Зато уж точно создастся 100 лишних ссылок.

Вообще, автор статьи не до конца разобрался в теме.
Основная ошибка и "затык" начинающих — попытка обращаться с прототипами объектов, а не их конструкторов. Ибо прототипов у самих объектов нет.

// конструктор
var MyObj = function(){ this.x = 10; };
MyObj.prototype = { x:0, y:0, move:function(x,y){this.x=x; this.y=y;} };
// создание объекта
var myObj = new MyObj();
for(var i in myObj){ if myObj.hasOwnProperty(i) alert(i); } // -> «x»
alert(myObj.x); // -> 10
alert(myObj.y); // -> 0
alert(myObj.move); // -> «function», т.е. метод присутствует.

Как это работает:
«MyObj» — обычная функция. Ещё раз: обычная функция.
При вызове (любой) функции с оператором new происходят три вещи:
1. Создаётся новый, «чистый» объект в памяти.
2. Ссылка на него передаётся вызываемой функции как «this». Всё, что функция назначит этому «this» внутри себя, соответственно будет назначено новому объекту.
3. По окончании работы ссылка на объект возвращается переменной по оператору присваивания. В нашем примере – «myObj». Также мы можем вернуть ссылку явно – оператором «return».

Если проверить все «собственные» свойства свежесозданного «myObj» при помощи метода hasOwnProperty() выяснится, что он имеет лишь свойство «x», назначенное ему конструктором.

for(var i in myObj){ if myObj.hasOwnProperty(i) alert(i); } // -> «x»

Тогда откуда берётся «y»? Не найдя этого свойства в самом объекте, машинка обращается к создавшей его функции. Ссылка на неё хранится в каждом объекте, в свойстве «constructor» (мы даже можем проверять принадлежность объекта: myObj.constructor == MyObj // -> true). Затем машинка обращается с свойству «prototype» конструктора и – о чудо – находит там «y». Полный путь: «myObj.constructor.prototype.y». Надеюсь, не нужно объяснять, почему это свойство получается общим для всех объектов, созданных через MyObj().
Вообще, все «общие» свойства, такие как методы или «статические» переменные нужно оставлять в прототипах. Делать на них лишние ссылки из объектов не нужно – машинка сама найдёт свойство по цепочке наследования. Другое дело, что можно переопределить свойство (как это сделано с «x» в примере) – тогда его значение будет уникальным, принадлежащим только этому объекту, а машинка доберётся до него раньше, не углубляясь в прототипы.

Подробнее о самом «прототипе». Это обычный объект, с обычными свойствами, создается обычным путём (запись в примере «someObj = {}» является лишь сокращением от «someObj = new Object()», как и «someObj = []» является «someObj = new Array()»). Т.е. можно создавать объект-прототип любого класса. Так реализуется наследование.

// новый конструктор
var myObj2 = function(){};
MyObj2.prototype = new MyObj(); // ога, вот отсюда возьмём всё что не найдем в самих объектах
// его объект
var myObj2 = new MyObj2();
for(var i in myObj2){ if myObj2.hasOwnProperty(i) alert(i); } // а ничего нет – кунструктор-то ничего не назначил, объект пустой
В общем плане верно, но если вдаваться в подробности, то как вы прокомментируете это:
var proto1 = {x: 1};
var proto2 = {x: 2};
function constr() {}
constr.prototype = proto1;
var obj = new constr();
alert(obj.x); // 1
constr.proto = proto2;
// либо obj.constructor.prototype = proto2;
alert(obj.x); // 1
Основная ошибка и "затык" начинающих — попытка обращаться с прототипами объектов, а не их конструкторов. Ибо прототипов у самих объектов нет.

Как раз ровно наоборот. Объект-прототип у объекта есть.

Тогда откуда берётся «y»? Не найдя этого свойства в самом объекте, машинка обращается к создавшей его функции. Ссылка на неё хранится в каждом объекте, в свойстве «constructor»

Совершенно неправильно. К функции никто не обращается, ссылка на неё в объекте не хранится. Выше дали ссылку на форум dklab (обсуждение наблы), где можно прочитать, почему именно неправильно.
Значит, нагнал я.
Прошу простить великодушно, буду разбираться.
(продолжение)

alert(myObj2.x) // ->10, ибо myObj2.constructor.prototype = объект, созданный MyObj, и при создании ему был назначен x = «10».

Ещё раз: у самого объекта prototype нет. Только у конструктора. Но каждый объект своего конструктора «знает».
ИМХО, больше в этой статье писать ни о чём не нужно.

p.s.
На закуску — пример «упаковки классов».

Object.extend = function(_class, properties)
{
if(!_class) _class = function(){};
_class.extend = this.extend;
_class.prototype = new this();
if(properties)
for(property in properties)
if(properties.hasOwnProperty(property)) _class.prototype[property] = properties[property];
return _class;
}

Пример:

var Point = Object.extend(null, {x:10, y:10});
var p1 = new Point();
alert(p1.x); // -> 10

var Circle = Point.extend(null, {r:20});
var c1 = new Circle();
log(c1.x); // -> 10

p.p.s. блин, глядя на ситуацию не мог промолчать. Надеюсь, поможет кому.
(Опечатка, имена конструкторов — с заглавной.)

var myObj2 = function(){};

должно быть

var MyObj2 = function(){};

function Test_One(){
this.x=5;
this.y=3;
this.sum=function(){
return this.x+this.y;
}
}

function Test_Two(){
this.x=5;
this.y=3;
}

Test_Two.prototype.sum=function(){
return this.x+this.y;
}

var A=new Test_One, B=new Test_One;
var C=new Test_Two, D=new Test_Two;

alert(A.sum==B.sum); // false
alert(C.sum==D.sum); // true
Во-первых - почитайте YUI Blog: with Statement Considered Harmful

Во-вторых - недостаток прописыванию методов в prototype без with вы привели лишь один - "здесь неприятно многократное повторение Test.prototype". А в конечном варианте "prototype" всё равно повторяете. Выглядит не намного красивее, зато добавляет уродливый оператор с присваиванием в его аргументе. И вы называете это грамотным подходом?
Строго говоря, начиная с третьего варианта, программист может выбирать любой на свой вкус. Мой личный вкус тяготеет к последнему.
Прочёл «YUI Blog: with Statement Considered Harmful».

If you can’t read a program and be confident that you know what it is going to do, you can’t have confidence that it is going to work correctly. For this reason, the with statement should be avoided.


Я могу прочесть приведённый мною участок кода, будучи уверенным, что знаю, что происходит.
Ты. Если работаешь над проектом один. А вот при работе в команде это может нанести очень серьёзный вред - этот вариант мягко говоря выглядит неинтуитивно.
Я могу однозначно прочесть приведённый мною очевидный участок кода и вправе ожидать того же от профессионалов, работающих со мной в команде.
Я бы не назвал профессионалом программиста, у которого нет стремления к чистому, красивому и интуитивно понятному коду.

Использование with в JavaScript считается таким же дурным тоном, как масовое использование глобальных переменных или goto в других языках.
Что касается конструктора, там речь идёт о функциях, содержащих код. Пустая функция одинаково эффективно создаётся через var F = new Function, function F(){}, var F = function(){}.
Если одинаково эффективно, следует выбрать из вариантов тот, что повсеместно принято использовать. Использование же нестандартных конструкций в коде просто так, без каких-либо конкретных преимуществ - это пустой выпендрёж.
У вас видимо нет опыта работы в команде.
Как вы себе представляете эффективную работу в таких условиях, если каждый разработчик поступает так, как ему "нравится"?
Если я работаю в команде, где принято писать function F(){}, я так и пишу. Что не означает, что две пары пустых скобок начинают мне нравиться.
Дело в том, что то, о чём идёт речь, принято везде, а не в какой-то отдельной команде.
Спасибо за статью! Пусть она местами и спорная, но такие статьи нужны, поскольку разработчиков становится все больше, а суммарный уровень знаний растет медленно ;) Плюс в карму.

А замечания не буду писать - тут и так много написали.
P.S. Разве что про "функциональное программирование" - исправьте, глупо же смотрится.
Начало статьи ещё ничего, хотя я бы не стал так категорично называть некоторые вещи "малограмотными" или вещами, которые "делать нельзя", всё это просто разные варианты "заведения" свойств у объектов/их прототипов, каждый имеет свои плюсы/минусы, и само собой каждый имеет право на жизнь, если используется к месту.

Ну, а та часть, где предлагается использовать "with statement" и приводятся какие-то сомнительные примеры наносит реальный вред начинающим и как минимум уводит их в тёмный лес. Судя по тексту про "загадочное поведение" автор статьи "плавает" - не знает точно, как именно работает with, отсюда и ни одного слова о том, что подстерегает программиста при работе с with, и какие вообще рекомендации по работе с with (скорее по НЕ работе с with) уже давным давно выработаны(бытуют) в js-сообществе. Если б знал - не советовал бы...
Для начинающих необходимо именно категорично выставлять некоторые вещи неграмотными или недопустимыми, даже если это спорное утверждение в данном случае. Это нужно для того, чтобы начинающие не начали делать ляпы в начале. Когда они дорастут до того уровня, чтобы самостоятельно определять, что грамотно или допустимо, а что — нет, они составят свою точку зрения. А до тех пор лучше сразу одёрнуть.

К слову:
Мне мама говорила всегда: "Не ешь тесто - кишки слипнутся, придётся операцию делать!" Я верил до... 20 лет. Потом, когда подумал, сказал: «Мама, так ты меня всю жизнь обманывала?» — «Конечно, зато ты у меня никогда тесто не ел».

Напишите, пожалуйста, почему нельзя использовать with.
Коротко:

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

http://forum.vingrad.ru/index.php?showtopic=120066&view=findpost&p=964268

По поводу необходимости одёргивать начинающих - вы называете малограмотными совершенно нормальные для js вещи. Это раз. А во-вторых, прямо рекомендуете то, что называется "bad code". А в-третьих, в этом "bad code" явно разобрались не до конца. Считайте, что я вас (о)дёрнул. ;-) ;-)
Не согласен местами. Мне понятен первоначальный посыл про 100 функций и оптимизацию, согласен, но создание свойства/метода непосредственно у объекта и создание наследуемого этим объектом свойства/метода у его прототипа - это в некотором смысле разные вещи, зависит всё от задач. Точно так же кто-то посчитает для себя более правильным отказ от "родного" и переопределение объекта-прототипа сразу же и т.д. и т.п.
Убирать совсем не надо, смысл здешнего обсуждения жалко, лучше добавить резюме или вывод какой по результатам дискуссии...
Ну а как тогда, если учесть, что статья предназначена для начинающих? Тут даже дисклеймер «Не делайте так, как написано ниже» не сработает…
прошу прощение за оффтоп, но не подскажете, как средствами текущего хабра можно так красиво оформлять код? (как это сделано у автора публикации)?
тэги <pre> и <strong>. Поддержка вставок кода на Хабре несколько хромает, сам накалывался
Only those users with full accounts are able to leave comments. Log in, please.