Обёртки для создания классов: зло или добро?

    Раз за разом я читаю, что удобные библиотеки для создания классов на Javascript, видите ли, не соответствуют идеологии языка и тем, кто их использует просто необходимо учить язык. Такое говорят невежды, которые и сами толком не разобрались ни в самом языке ни в библиотеках, которые они критикуют. И так часто говорят, что я решил написать этот топик и просто давать ссылку
    var Foo = new Class({
    	Extends: Bar,
    	initialize: function(firstname, lastname) {
    		this.parent(firstname);
    		this.lastname = lastname;
    	},
    	sayHello: function(){
    		alert(this.lastname || this.firstname);
    	}
    });
    


    Обёртки в нормальных библиотеках совершенно не противоречат идеологии Javascript и прототипно-ориентированного программирования. Они просто предоставляют удобный интерфейс, алиас для его использования. Конечно, есть библиотеки, которые реализуют «ООП в Javascript» через какие-то черезжопные методы, но сейчас такие во внимание не берем, а рассматриваем их на примере Мутулз.

    Например, для конструкции
    MyClass = function() {/* constructor */};
    MyClass.prototype.firstMethod  = function() {/**/};
    MyClass.prototype.secondMethod = function() {/**/};
    MyClass.prototype.thirdMethod  = function() {/**/};
    


    Введен алиас, который позволяет не повторять MyObject.prototype. Не обойтись без этой конструкции, а просто не повторять её. Внутри библиотеки будет всё тот же
    MyClass = new Class({
    	initialize  : function() {/* constructor */},
    	firstMethod : function() {/**/},
    	secondMethod: function() {/**/},
    	thirdMethod : function() {/**/}
    });
    


    Следующая конструкция для наследования (заметьте, она полностью соотсветствует идеологии Javascript и «ловится» с помощью instanceof)
    MyAnotherClass = function() {/* constructor */};
    var Tmp = function() { }
    Tmp.prototype = MyClass.prototype
    MyAnotherClass.prototype = new Tmp()
    MyAnotherClass.prototype.constructor = MyAnotherClass;
    

    Инкапсулируется в такую простую запись:
    MyAnotherClass = new Class({
    	Extends: MyClass
    });
    


    И еще много-много-много. Суть в том, что оно не нарушает идеологию JavaScript. Оно под неё подстраивается и предоставляет удобный интерфейс, а внутри это всё то же самое, что делали бы профессиональные. Ведь идеология прототипно-ориентированного программирования — это не многословность и постоянные упоминания слова «prototype», а невероятная расширяемость.

    Перегруженый конструктор



    А идеологию нарушает как раз другой, очень вредный и очень популярный подход — перегруженный конструктор:
    var MyClass = function() {
    	/* Начало конструктора */
    	this.firstMethod  = function() {/**/};
    	this.secondMethod = function() {/**/};
    	this.thirdMethod  = function() {/**/};
    	/* Конец конструктора, вот такой у нас зашибезный конструктор */
    };
    var MyAnotherClass = function() {
    	// Наследование
    	MyClass.apply(this, arguments);
    	this.elseOneMethod  = function() {/**/};
    };
    var myAC = new MyAnotherClass();
    console.log(myAC instanceof MyClass); // false
    


    Который нельзя наследовать так, чтобы работал instanceof, в которой функции создаются каждый раз при создании объекта, из-за чего память течет рекой и рыдает процессор. Вместо того, чтобы создать функцию в одном месте и дать на неё ссылку — будем создавать её каждый раз. Этих функций нету в прототипе (а во встроенных объектах, таких как Array, Number и остальных всё находится именно в прототипе), их нельзя переопределить или получить вне контекста, они неправильно определяются с помощью hasOwnProperty и работают непозволительно медленно (в сотни раз):
    var MyClass = function() {
    	this.method1 = function(){};
    };
    MyClass.prototype.method2 = function(){};
    
    var myClass = new MyClass;
    console.log(myClass.hasOwnProperty('method1')); // true
    console.log(myClass.hasOwnProperty('method2')); // false
    
    var Foo = function() {
    	this.method1 = function(){};
    	this.method2 = function(){};
    	this.method3 = function(){};
    	this.method4 = function(){};
    	this.method5 = function(){};
    	this.method6 = function(){};
    	this.method7 = function(){};
    	this.method8 = function(){};
    	this.method9 = function(){};
    }
    
    var Bar = function() {};
    Bar.prototype.method1 = function(){};
    Bar.prototype.method2 = function(){};
    Bar.prototype.method3 = function(){};
    Bar.prototype.method4 = function(){};
    Bar.prototype.method5 = function(){};
    Bar.prototype.method6 = function(){};
    Bar.prototype.method7 = function(){};
    Bar.prototype.method8 = function(){};
    Bar.prototype.method9 = function(){};
    
    /**
     * Chrome 8
     *  Foo: 260
     *  Bar:  26
     * Firefox 3.5
     *  Foo: 22081
     *  Bar:   158
     */
    console.time('Foo');
    for (var i = 1000000; i--;) new Foo();
    console.timeEnd('Foo');
    
    console.time('Bar');
    for (var i = 1000000; i--;) new Bar();
    console.timeEnd('Bar');
    

    Я уж молчу про то, что будет в Internet Explorer. Сами проверьте. Можно оставить компьютер включённым на ночь =)
    Даже то, что он позволяет реализовать псевдо-приватные методы и переменные не оправдывает этот подход ни на секунду.

    Еще раз недостатки перегруженого конструктора, списком:
    * не работает instanceof в детях
    * значительно больший расход памяти
    * работает в десятки-сотни раз медленнее
    * методы отсутствуют в прототипе
    * методы неправильно определяются через hasOwnProperty

    Вывод


    Пользуйтесь тем, что вам удобно и приятно пользователям. Если считаете, что MooTools.Class или любая другая подобная библиотека сделает ваше приложение читабельнее и облегчит поддержку — используйте её! И не обращайте внимание на всяких сами знаете кого, которые кричат про идеологию ДжаваСкрипта (а она, несомненно, прекрасна)

    UPD: Этот топик был написан как ответ на сообщение aux:
    JS проповедут прототипное ООП, вставлять классовые костыли — не гут. Учите язык и его возможности.

    Я не утверждаю, что подход в фреймворках единственно-верный. Просто не следует избегать таких обёрток, как MooTools.Class, если кто-то сказал вам, что она, видите ли, не соответствует идеологии Javascript или что-то подобное. _
    Поделиться публикацией

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

      0
      У меня в IE9 Beta даже не запустилось :)
      habreffect.ru/498/67b559cce/s.png
      Может что-то неправильно делаю?
        +3
        Может что-то неправильно делаю?

        используете IE9? =)

        ошибка странная, конечно. ни тебе строки, ни трейса. просто какой-то объект не поддерживает какой-то метод.возможно, дело в том, что console.time не поддерживается в IE9. попробуйте заменить на:
        var start = new Date();
        for (var i = 1000000; i--;) new Foo();
        alert(new Date() - start);
        
        var start = new Date();
        for (var i = 1000000; i--;) new Bar();
        alert(new Date() - start);
        
          +4
          >Такое говорят невежды, которые и сами толком не разобрались ни в самом языке ни в библиотеках
          Такой толстый вброс, что заслонил собой небо.

          Вы вот все напираете что библиотеки предоставляют синтаксический сахар для модели предусмотренной в языке, но прототипы и классы – разные вещи. Делегирование и наследование – разные вещи.
            +1
            Ну? И что? Я говорю иначе?
              +1
              Я хочет сказать что тут происходит подмена понятий. Где-то, возможно, библиотеки действительно ничего не делают кроме предоставления более компактного интерфейса для прототипного ооп, но в основном же люди просто пытаются переместиться в зону комфорта классового ооп.

              Я к тому, что когда в руках такой гибкий инструмент, иногда стоит рассмотреть возможности которые он дает, а не делать все по старому, как привык.
                0
                Напишите, как вы предлагаете реализовывать «классы» на Джаваскрипте. Интересен ваш подход.
                  +1
                  Их не надо реализовывать. Про это и говорил предыдущий оратор. JS — прототипный язык программирования. Программист-классовик должен изменить свое мышление, а не пытаться сделать кучу оберток и «доточить» JS до классового языка.
                    +1
                    то есть встроенные объекты Array, Object, Number — не нужны?
                      +2
                      Класс и объект, строго говоря, не одно и то же:)
                        +1
                        Ну в джаваскрипте это одно и то же в том понимании, о котором мы говорим. Что-то изменится, если вместо MooTools.Class метод будет называться MooTools.Function или MooTools.Object?
                        var array = new Array('one', 'two', 'three');
                        array instanceof Array; // true
                        array instanceof Object; // true
                        

                        В данном случае array — это экземпляр объекта(класса/функции/етс) Array.
                        Array — «ребенок» Object.

                        Так придумали и так следуют сами создатели языка, об этом и говорится в топике. Мутулз предоставляет удобный интерфейс для того, чтобы продолжать эту «традицию»
                          +2
                          То, что в некоторых языках классы обладают свойствами и то, что с ними можно обращаться, как с объектами — это очень удобная фича. Но семантически объекты и классы — это разные вещи. Класс — это тип переменной, а объект — экземпляр этого типа. То, что классы сами по себе являются экземплярами некоего типа (как, например, в Python) очень клево, но совершенно не меняет их семантику.
            0
            Идеология языка? Что это? Пока интерпретатор может сожрать ваш код, идеология языка в порядке.
            Можно рассуждать о каких-то best practicies, perfomance penalties, etc., но не об идеологии.
              +6
              Вы возможно не поверите, но языки создаются не от балды, как сказала левая нога, а следую некоторым идеям.
              Если язык достаточно гибок, то на нам можно попытаться выразить идеи не заложенные в ядро создателями языка. Иногда таким образом с языком делают противоестественные вещи.
                –3
                То есть, вы хотите сказать, что делаете противоестественную вещь, добавляя в язык классы? Ведь, насколько я помню, в спецификации слово «class» встречается только в одном-единственном месте — в списке зарезервированных слов.

                Что касается меня, то я пас.
                Мне не нравится ваш любимый ООП-через-классы тем, что он «стоит» интерпретатору дополнительных телодвижений. Javascript classes came at cost, ага.
                0
                Видимо, следовало написать, что это было ответом на сообщение Aux:
                JS проповедут прототипное ООП, вставлять классовые костыли — не гут. Учите язык и его возможности.

                Именно потому что я часто встречаю такие слова как «проповедует», «идеология» и т.п. при критике Мутулз — именно так и писал.
                  0
                  Нет, я его сообщение вообще не видел при отправке. Так что комментарий был вам.

                  Но с ним я тоже не согласен. Главное, чтобы код работал, работал быстро, и поддерживался легко. И не надо никаких идеологий сюда примешивать. Повторюсь, я готов рассуждать о best practicies, о производительности, расширяемости, но не об идеологии.
                    0
                    я говорю, что мой топик был ответом на сообщение Aux) если говорить об идеологии, то самый правильный способ — расширение прототипов. Если о всём остальном — тоже =)
                      0
                      А, ну тогда извиняюсь, к вам «претензий» никаких :)
                      Плюс в карму «за беспокойство» :)
                +3
                Обычно вот так делают чтобы не повторять prototype:
                MyClass = function() {/* constructor */};
                MyClass.prototype = {
                firstMethod: function() {/**/},
                secondMethod: function() {/**/},
                thirdMethod: function() {/**/}
                }
                  0
                  Да, обычно, если небольшое приложение и библиотеки не подключаются, то лично я так и делаю. Но во время наследования такое уже не прокатит. Т.е. после:
                  MyClass = function() {/* constructor */};
                  MyClass.prototype = new ParentClass;
                  
                    0
                    конечно, тогда можно делать что-то типа такого:
                    MyClass = function() {/* constructor */};
                    MyClass.prototype = new ParentClass;
                    jQuery.extend(MyClass.prototype, {
                      firstMethod: function() {/**/},
                      secondMethod: function() {/**/},
                      thirdMethod: function() {/**/}
                    });
                    
                  +3
                  Вы громко прокричали со скалы о невеждах, но немного пресно, что ли. В принципе, согласен с предыдущим комментарием о best practices, нежели об идеологии. Существует несколько способов того же наследования (constructor stealing, parasitic inheritance, и др.) и все они подходят к определённым задачам точно так же, как и стандартный прототипированный подход без эмуляции ООП или другой концепции. Т.е. слова «или любая другая подобная библиотека сделает ваше приложение читабельнее и облегчит поддержку — используйте её» — как от капитана Очевидность, логично, объясняет смысл топика: рациональный и эффективный выбор подхода. Но никак не о конкретном решении, которое показано.
                    0
                    видимо не для всех это очевидно. А многие еще и боятся использовать, т.к. «противоречит идеологии Javascript»
                      0
                      Наверное, нужно написать про то, что есть «идеология» и что есть «реализация», и привести конкретные примеры. Думается, поможет лучше. Например, прототипы в чистом виде лучше использовать в таких-то и таких-то случаях, когда не требуется множественного наследования, это позволяет сэкономить X% памяти и Y% скорости компиляции/выполнения. В то время как метод, скажем, class factory, нужен в случаях, когда нужна определённая гибкость до такого-то уровня, но стоить это будет «дороже». И т.п.
                        0
                        я всего-лишь утверждаю, что говорить «Обёртки, как в MooTools — нарушают идеологию языка» — это неправильно.
                          0
                          Согласен, это выражение слегка абсурдное. Но оно скорее от путаницы в понятиях.
                          Я не говорю, что выш посыл лишён смысла, я говорю о том, что, может быть, подход к изложению проблемы стоит переделать, зайти с другой стороны.
                            0
                            Я обновил топик, смотрите последнее предложение =)
                    +2
                    > не работает instanceof в детях

                    каких ещё детях? это самостоятельные классы, которые могут подмешивать в себя другие классы. они не являются детьми. и подмешивать могут произвольное число классов. но если уж так хочется чтобы работал instanceof (хотя это дурная практика, ибо утиная типизация идёт лесом и мы жёстко привязываемся к конкретным классам), то всегда можно приписать MyAnotherClass.prototype= MyClass.prototype

                    > течет память

                    никуда она не течёт. потребление памяти несколько больше, зато публичный интерфейс не захламлён мусором, исполняется код быстрее за счёт замыканий, методы привязываются к объекту и могут быть без лишнего геморроя отданы в качестве колбэка.

                    > методы неправильно определяются через hasOwnProperty

                    и что там не правильного? почему ты считаешь одну реализацию правильнее другой?

                    > работает в десятки-сотни раз медленнее

                    не знаю как ты так тестировал, но у меня разница не более 30% это на win7, core2 duo p960 @ 2.54ghz, chrome 8.0.552.224

                    в 4 лисе аналогично, причём время компиляции в случае new Bar() в 20 раз дольше

                    в последней опере. new Bar исполняется в 6 раз быстрее, но компилируется в 30 раз медленнее

                    в ие8 в 20 раз быстрее исполняется и 2 раза медленнее компилируется

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

                    > методы отсутствуют в прототипе

                    а зачем они там?

                    > их нельзя переопределить или получить вне контекста

                    это типа плохо? что за дурная привычка лезть в чужой объект со своими грязными руками? кто-то сделал bind а потом ты поменял метод в прототипе. опа, а колбэк будет вызывать старый метод. ладно, вырвал метод из его контекста, а у него зависимость от другого метода из контекста. опа, тянем за собой весь контекст.
                      0
                      они не являются детьми. и подмешивать могут произвольное число классов.

                      Если вы просто реализуете какой-то метод, тогда да. А вот когда вы полностью реализуете интефейс родителя — тогда ребенок.
                      Так же, как Array — ребёнок Object.

                      зато публичный интерфейс не захламлён мусором

                      Каким мусором?

                      исполняется код быстрее за счёт замыканий

                      Это всё в ваших фантазиях. Что-то кроме слов есть?

                      почему ты считаешь одну реализацию правильнее другой?

                      Потому-что такому подходу следует сам язык:
                      alert([].hasOwnProperty('push')); // false
                      


                      На счёт времени — есть ещё старые браузеры, которые популярны — fx3.6, fx3.5, ie7, ie6, opera10, opera10.5, opera10.6.
                      И даже в современных браузерах это заметно.
                      Время компиляции роли не играет. Компилируется классов немного и один раз. Будет оно выполнятся 0.005 с или 0.09 с — неважно.
                      А вот создаются экземпляры — постоянно, может даже тысячами. И это в момент не когда пользователь ожидает, а когда пользователь работает.

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

                      Это как так? Контекст всё-равно оторвётся. Или вы говорите о том, чтобы делать bind каждый раз?
                        0
                        Про мусор наверное имелся ввиду вырвиглазный this, повсеместно, который кроме всего прочего ещё и публичный, в то время как с замыканием можно вполне красиво сделать приватные свойства.
                          0
                          а чем отличается this в каждом из подходов?
                            0
                            Тем что во втором подходе в this будет соваться всё подряд, а в первом — только то, что нужно показать наружу.
                          –1
                          > А вот когда вы полностью реализуете интефейс родителя — тогда ребенок.

                          родственные отношения и степень реализованности интерфейса — перпендикулярные понятия. и пересекаются они только в больном классово ориентированном воображении.

                          > Каким мусором?

                          приватным, очевидно

                          > Это всё в ваших фантазиях. Что-то кроме слов есть?

                          есть знания и тесты. а на чём основана твоя самоуверенность?

                          > Потому-что такому подходу следует сам язык:

                          почему же в самом языке нет классов?

                          > На счёт времени — есть ещё старые браузеры, которые популярны

                          это что за отмазка в пользу бедных? типа отсосал при очной ставке, а теперь предлагаешь мне пойти потестировать на всех браузерах, на которых ты не тестировал, чтобы доказать, что твои тесты — ложь, пиздёжь и провокация?

                          > И даже в современных браузерах это заметно

                          в реальных приложениях это не заметно.

                          > Время компиляции роли не играет.

                          ага, это пока не пытаются сделать из браузера вебОС

                          > Компилируется классов немного и один раз.

                          создаются объекты тоже не так часто как используются

                          > А вот создаются экземпляры — постоянно, может даже тысячами.

                          может даже стоит выбрать другой алгоритм, чем заниматься микрооптимизациями. одно создание объекта типа сабжевых в хроме — 1-2 микросекунды. присвоение его полю простого значения — 2.5 микросекунды. спрашивается, много ты сэкономишь в реальном коде?

                          > Это как так? Контекст всё-равно оторвётся. Или вы говорите о том, чтобы делать bind каждый раз?

                          var foo= new function(){
                          var state= 1
                          this.bar= function(){ state++ }
                          }

                          var bar= foo.bar

                          bar()
                            0
                            > исполняется код быстрее за счёт замыканий
                            Он реально быстрее за счет замыкания (до 3х раз). Ибо переменные будут не в глобале, а в объекте активации функции/объекте параметров функции.

                            > течет память
                            Память текла только в ИЕ6 до патча 200X года

                            > Потому-что такому подходу следует сам язык:
                            > alert([].hasOwnProperty('push')); // false
                            Не понял вашего отрывка. Тут очевидно, что будет false ибо push делегируется из прототипа Array
                              0
                              при вызвове hasOwnProperty('methodName') объект должен возвращать true или false?
                                0
                                Если это Own Property то возвратит true если Property делегируется от прототипа или его нет в цепочке прототипов будет false.
                                var a = [];
                                a.hasOwnProperty('push'); // false push in prototype
                                a.pushh = function () {};
                                a.hasOwnProperty('pushh'); // true pushh is own property
                                  0
                                  именно об этом я и говорю.
                                0
                                а разве при прототипном подходе какие-то переменные будут в глобале?
                                  –1
                                  Нет, я просто констатировал факт.
                                    +1
                                    тогда быстрее до трех раз по сравнению с чем?
                                      –1
                                      Когда переменная находится в глобале к ней доступ раза в 3 медленнее, чем когда она в ОА.
                            +1
                            Перегруженый конструктор однозначно зло, ибо методы клонируются, а не делигируются от прототипа (занимает больше памяти — значительно медленнее работает). Не защищайте данный подход, все знаю, что так делать плохо.
                              –1
                              В подавляющем большинстве случаев временем и потреблением памяти на создание объектов в js можно пренебречь.
                                +2
                                Тогда в чем преимущество

                                var Foo = function() {
                                this.method1 = function(){};
                                }

                                над

                                var Bar = function() {};
                                Bar.prototype.method1 = function(){};

                                такое, что оно компенсирует затраты памяти и время создания?
                                  0
                                  В инкапсуляции и немного большей аккуратности чтоли в коде.
                                    0
                                    Инкапсуляции те в скрытии свойств приватных методов?

                                    В JavаScript эмуляция приватных методов штука не очень полезная. Во-первых все приватные методы/свойства можно так или иначе достать, минуя правила.
                                    var foo = (function () {
                                      var y = 20;
                                      return function () {
                                        alert(y);
                                      };
                                    })();
                                     
                                    foo(); // 20
                                    alert(foo.__parent__.y); // 20
                                    foo.__parent__.y = 30;
                                    foo(); // 30
                                    
                                    Во-вторых приватные методы — траты памяти. В-третьих приватные методы сложно сразу найти в коде и они искривляют структуру программы.

                                    Раз от хакеров не защититься (инкапсуляция ради скрытия), эмуляция приватов занимает больше памяти и искажает программу. То почему бы не использовать подход с подчеркиванием и среди разработчиков условиться не использовать приватные методы напрямую (оргмоменты)
                                    Foo.prototype._smthPrivate = function () {};
                                    Мы компенсируем недостатки эмуляции приватов. Получаем небольшой бонус :) подчеркивание понимает jsdoc-toolkit и автоматом ставит флаг private для всех методов и свойств (может скрывать или показывать в документации), тогда когда приватные функции (эмуляции приватов) нужно явно указывать, явно прописывать Doc блок.

                                    > Немного большей аккуратности чтоли в коде.
                                    Написание через prototype./this. это дело привычки.
                                      0
                                      Я уверен что во многих языках можно достать приватные свойства обходными путями, но это будут хаки так или иначе. А тут защита от добрых программистов, а не злых хакеров :)

                                      Что касается трат памяти… ну раз такая экономия на спичках, то доступ к переменной замыкания будет немного быстрее чем через this, но это опять же не существенно.

                                      Что касается кривой структуры… с теми же прототипами тоже можно где — нибудь в дебрях кода объявить this.y = 1 мимо конструктора, а потом все будут искать откуда у неё ноги выросли. Всё зависит от организации кода.
                                –1
                                исполняемый код не клонируется. клонируется только контекст конструктора. то есть множество локальных переменных. значит дополнительное потребление памяти = ( 4 * число_методов ) байт, что может быть значительным только в вырожденном случае, когда у объекта почти нет состояния, зато методов — вагон.
                                  +1
                                  В этом и проблема, что потребление памяти в режиме перегруженного конструктора растет N * [4 + вес метода] где вес метода на много больше веса указателя, а при прототипном делегировании всего лишь N * 4 (минус подхода раз)
                                  При этом каждый раз создаются одни и теже методы, которые каждый раз JIT-Компилируются (минус подхода два)

                                  А где плюсы?
                                    –2
                                    по твоему создатели яваскриптовых движков совсем идиоты? х) ты бы хоть проверил прежде чем с таким умным видом говорить.
                                      0
                                      А ты хочешь сказать, что копируется только ссылка на функцию? Ну уж нет! Там и контекст, и замыкание, а ещё то, что ты каждому такому методу можешь своё свойство поставить — тоже характерно. Тут простым указателем не обойдёшься. Да и не имеет смысла оптимизировать это. Сейчас, в эпоху JQuery, MooTools и PrototypeJS практически никто при крупной разработке не пользуется объявлением метода в конструкторе.
                                      var Foo = function() {
                                          this.method = function(){};
                                      };
                                      
                                      var bar = new Foo;
                                      var qux = new Foo;
                                      
                                      bar.method.qwe = 1;
                                      qux.method.qwe = 2;
                                      
                                      console.log(bar.method.qwe); // 1
                                      console.log(qux.method.qwe); // 2
                                      
                                        0
                                        И откуда тут вывод что тело функции копируется в каждый объект, ей что, контекст нельзя подсунуть при вызове?
                                          0
                                          Контекст — можно. Только при чём тут контекст. Вы еще про аргументы спросите. В Джаваскрипте контекст от аргументов почти не отличается. Тем не менее, копируется явно не указатель функции.
                                            0
                                            В каком месте вашего поста это стало явным?
                                              0
                                              в том, что свойству method присваиваются разные значения и если бы она была всего-лишь указателем на функцию, а не копией, то значения в конце были бы одинаковыми
                                                0
                                                Объект функции содержит в себе указатель на функцию так или иначе. Вопрос был в том откуда вывод что объекты bar.method и qux.method содержат в себе указатели на разные функции?
                                                  0
                                                  а такого вывода и не было. я сказал, что одного указателя на объект функции будет недостаточно. там, как минимум, еще замыкания, свойства и т.п. и, может, сама функция не копируется, но её объект — копируется, вместо того, чтобы браться из общего пула. но тесты от azproduction показывают весь фейл такого подхода.
                                        0
                                        между прочим, если ваша статистика на счёт увеличения времени компиляции верна, то это показывает, что все современные браузеры оптимизируют код, объявленный через прототипы намного лучше, чем код, которым загажен конструкторю
                                          0
                                          не, это ничего не значит. может они больше времени тратят на оптимизацию одного, а может там лучше оптимизирована оптимизация другого х) по времни работы ничего не сказать о качестве её исполнения %-)
                                          +2
                                          Задели вы меня свои комментом :) Вот вам Пруф:

                                          Тест 1: index_prototype.html
                                          
                                          alert('Start');
                                          
                                          var Bar = function() {
                                            this.a1 = 1;
                                            this.a2 = {};
                                            this.a3 = [];
                                            this.a4 = '0';
                                            this.a5 = false;  
                                            this.a6 = /\s/ig;
                                            this.a7 = window;
                                            this.a8 = .1;
                                            this.a9 = 100;
                                          };
                                          Bar.prototype.method1 = function(){ this.a1 = this.a1 * this.a8 };
                                          Bar.prototype.method2 = function(){ this.a2 = this.a2 + this.a7 };
                                          Bar.prototype.method3 = function(){ this.a3 = this.a3 - this.a6 };
                                          Bar.prototype.method4 = function(){ this.a4 = this.a4 / this.a5 };
                                          Bar.prototype.method5 = function(){ this.a5 = this.a5 || this.a9 };
                                          Bar.prototype.method6 = function(){ this.a6 = this.a6 && this.a4 };
                                          Bar.prototype.method7 = function(){ this.a7 = this.a7 + this.a3 };
                                          Bar.prototype.method8 = function(){ this.a8 = this.a8 - this.a2 };
                                          Bar.prototype.method9 = function(){ this.a9 = this.a9 * this.a1 };
                                          
                                          var data = [];
                                          for (var i = 1000000; i--;) {
                                              data.push(new Bar());
                                          }
                                          
                                          // чтобы было все честно
                                          window.setTimeout(function () {
                                             data[~~(data.length * Math.random())](); 
                                          }, 10000000);
                                          


                                          Тест 2: index_this.html
                                          
                                          alert('Start');
                                          
                                          var Bar = function() {
                                            this.a1 = 1;
                                            this.a2 = {};
                                            this.a3 = [];
                                            this.a4 = '0';
                                            this.a5 = false;  
                                            this.a6 = /\s/ig;
                                            this.a7 = window;
                                            this.a8 = .1;
                                            this.a9 = 100;
                                            this.method1 = function(){ this.a1 = this.a1 * this.a8 };
                                            this.method2 = function(){ this.a2 = this.a2 + this.a7 };
                                            this.method3 = function(){ this.a3 = this.a3 - this.a6 };
                                            this.method4 = function(){ this.a4 = this.a4 / this.a5 };
                                            this.method5 = function(){ this.a5 = this.a5 || this.a9 };
                                            this.method6 = function(){ this.a6 = this.a6 && this.a4 };
                                            this.method7 = function(){ this.a7 = this.a7 + this.a3 };
                                            this.method8 = function(){ this.a8 = this.a8 - this.a2 };
                                            this.method9 = function(){ this.a9 = this.a9 * this.a1 };
                                          };
                                          
                                          var data = [];
                                          for (var i = 1000000; i--;) {
                                              data.push(new Bar());
                                          }
                                          
                                          // чтобы было все честно
                                          window.setTimeout(function () {
                                             data[~~(data.length * Math.random())](); 
                                          }, 10000000);
                                          


                                          Тестировалось на мегооптимизированном браузере Google Chrome (ещё эталоны?). Как тестировалось: открываю вкладку, засовываю туда скрипт, скрипт выдает алерт запускаю ProcessExplorer засекаю начальное значение памяти этого процесса(вкладки). После отработки засекаю конечное значение.

                                          тест index_prototype: до 8 Мб, после 136Мб img253.imageshack.us/i/indexprototype.png/
                                          тест index_this: до 8 Мб, после 442Мб img340.imageshack.us/i/indexthis.png/

                                          > создатели яваскриптовых движков совсем идиоты
                                          Видимо совсем идиоты…
                                            0
                                            Видимо совсем идиоты…

                                            Та нет, уж скорее те, кто защищает этот хреновый способ. А создатели Javascript движок — умные люди, которые не тратят время на ненужныевещи.
                                              0
                                              Это сарказм :)
                                                0
                                                та я понял =)
                                              0
                                              Вот ещё третий тест

                                              Тест 3: index_pm.html
                                              alert('Start');
                                              
                                              var bar = function() {
                                                this.a1 = 1;
                                                this.a2 = {};
                                                this.a3 = [];
                                                this.a4 = '0';
                                                this.a5 = false;  
                                                this.a6 = /\s/ig;
                                                this.a7 = window;
                                                this.a8 = .1;
                                                this.a9 = 100;
                                                var a = {};
                                                a.method1 = function(){ this.a1 = this.a1 * this.a8 };
                                                a.method2 = function(){ this.a2 = this.a2 + this.a7 };
                                                a.method3 = function(){ this.a3 = this.a3 - this.a6 };
                                                a.method4 = function(){ this.a4 = this.a4 / this.a5 };
                                                a.method5 = function(){ this.a5 = this.a5 || this.a9 };
                                                a.method6 = function(){ this.a6 = this.a6 && this.a4 };
                                                a.method7 = function(){ this.a7 = this.a7 + this.a3 };
                                                a.method8 = function(){ this.a8 = this.a8 - this.a2 };
                                                a.method9 = function(){ this.a9 = this.a9 * this.a1 };
                                                return a;
                                              };
                                              
                                              var data = [];
                                              for (var i = 1000000; i--;) {
                                                  data.push(bar());
                                              }
                                              
                                              // чтобы было все честно
                                              window.setTimeout(function () {
                                                 data[~~(data.length * Math.random())](); 
                                              }, 10000000);
                                              

                                              Результат: до 8Мб, после 357Мб (как и ожидалось)
                                              Из всех 3х подходов метод с прототипным делегированием экономичнее и быстрее (как и ожидалось)
                                                0
                                                Косяк с моей стороны: нужно заменить у всех переменных this. на var, а в методах убрать this. В конце память будет 480Мб (357Мб из-за того, что тест был кривой).
                                                  0
                                                  А теперь сделайте функции пустыми и увидите что разницы никакой, т.к. вес функции тут не участвует, а память заполнили объекты — указатели на функции.
                                                    0
                                                    Убрал начинку функций стало 355 было 480. Куда интересно ушло 130Мб?!
                                                      0
                                                      По вашему скомпилированный код ничего не весит?
                                                        0
                                                        130Мб скомпилированного кода, вы издеваетесь? (Когда там чистого скрипта дай Бог килобайт наберется).
                                                        Для чистоты эксперимента я убрал цикл в версии без контента и в версии с контентом. Разницы в занимаемой памяти скриптом нет оба 8,1 Мб/вкладка.
                                                        Мой предыдущий коммент к тому, что в памяти не только указатели, а ещё и копии функций.
                                                          0
                                                          в данном конкретном случае a.method1-a.method9 даже не должны компилироваться в нативный код — они не вызываются (и имеют тривиальный контекст).
                                                            0
                                                            хотя если вы у методов тела не с this сделаете, а с ссылкой на переменную из объемлющего scope, то контекст перестанет быть тривиальным и V8 откомпилируют методы нелениво (это на самом деле дизайнерское решение, такое, можно сделать и так чтобы функции с нетривиальным контекстом тоже лениво компилировались для экономии места).

                                                            В любом случае сгенерированная версия (неоптимизированного в случае V8 >= 3.0) кода разделяется между всеми экземплярами замыканий созданных из одного литерала.
                                                              0
                                                              ну про это, в том числе, и речь.
                                                              0
                                                              Чистого скрипта там 27*9*1000000=243 мегебейта.
                                                                0
                                                                Кто-то из нас кого-то недопонял. Мой коммент «Куда интересно ушло 130Мб» о том, что тело фукнкции все-таки занимает место в памяти, а не «сделайте функции пустыми и увидите что разницы никакой». Я понял ваш «скомпилированный код» код как скомпилированный код без данных и без занимаемой им доп памяти (исполняемый код как .dll). Поэтому меня возмутил факт 130 Мб чисто скрипта.
                                                            0
                                                            Протестируйте еще один интересный момент — не почти пустые функции, а загруженные по самое «не могу». Такого типа:
                                                            function(){
                                                            var object = this.object;
                                                            setTimeout(function() {
                                                            this.object.value = parseInt(Math.random() * 100);
                                                            this.object.run();
                                                            }, 100);
                                                            };

                                                            Короче, если их забить каким-то кодом, пусть и глупым, но чтобы у функции было реальное тело.
                                                              0
                                                              Набрал абсолютно разных длинных функций, наподобие этой — результат 512Мб (+32Мб). Размер функции решает.
                                                              Если написать однотипные функции с переименованием переменных либо просто копипаст(если от названия переменных суть функции не меняется), то хром оптимизирует это дело и памяти занимает меньше (375Мб).
                                                              +1
                                                              Не рекомендую потребление памяти замерять Process Explorer'ом. Память может быть выделена, но не использоваться под объекты.

                                                              В последних версиях Chrome можно запустить его с флажком --enable-memory-info и получить доступ к console.memory (я правда не помню есть ли эта фича в Chrome 8).

                                                              Либо скачайте сорцы V8, соберите shell и погоняйте ваш тест в консоли с --trace-gc.

                                                              Еще можно в сорцы посмотреть :-) Вас интересуют классы v8::internal::JSFunction (создается для каждого замыкания) и v8::internal::SharedFunctionInfo (разделяется между замыканиями созданными из одного литерала), v8::internal::Code (представляет собой скомпилированный нативный код, неоптимизированная версия одна для всех замыканий созданных из одного литерала), v8::internal::Context (контекст замыкания).

                                                              Если прикидывать на пальцах, то, если ничего не путаю, на ia32 будет что-то около 36 байтов на JSFunction + 32 байта на тривиальный Context — минимальная цена каждого замыкания.
                                                          0
                                                          а чтобы исключить сообщения о том, что с перегруженным конструктором методы вызываются быстрее вот пруф, что это не так:
                                                          var Foo = function() {
                                                          	this.a = 0;
                                                          
                                                          	this.method = function() { this.anotherMethod(); };
                                                          	this.anotherMethod = function() { this.a++; };
                                                          
                                                          	var p = 0;
                                                          	this.increaseP = function () { p++; }
                                                          	this.getP = function() { return p; }
                                                          };
                                                          
                                                          var Bar = function() {
                                                          	this.a = 0;
                                                          	this._p = 0;
                                                          };
                                                          Bar.prototype.method = function() { this.anotherMethod(); };
                                                          Bar.prototype.anotherMethod = function() { this.a++; };
                                                          Bar.prototype.increaseP = function() { this._p++; };
                                                          Bar.prototype.getP = function() { return this._p; };
                                                          
                                                          var foo = new Foo;
                                                          var bar = new Bar;
                                                          
                                                          /**
                                                           * Firefox 3.5
                                                           *   foo : 197ms
                                                           *   bar : 195ms
                                                           *   fooP: 166ms
                                                           *   barP: 146ms
                                                           * Chrome 8
                                                           *   foo : 78ms
                                                           *   bar : 79ms
                                                           *   fooP: 86ms
                                                           *   barP: 83ms
                                                           */
                                                           
                                                          
                                                          console.time('foo');
                                                          for (var i = 100000; i--;) foo.method();
                                                          console.timeEnd('foo');
                                                          
                                                          console.time('bar');
                                                          for (var i = 100000; i--;) bar.method();
                                                          console.timeEnd('bar');
                                                          
                                                          console.time('fooP');
                                                          for (var i = 100000; i--;) foo.increaseP();
                                                          console.timeEnd('fooP');
                                                          
                                                          console.time('barP');
                                                          for (var i = 100000; i--;) bar.increaseP();
                                                          console.timeEnd('barP');
                                                          
                                                          
                                                          console.log(foo.a, foo.getP(), bar.a, bar.getP(), bar._p);
                                                          
                                                            0
                                                            Firefox 3.6 на миллионе итераций

                                                            * foo: 2082ms
                                                            * bar: 2084ms

                                                            * fooP: 1756ms
                                                            * barP: 1585ms

                                                            Разница микроскопическая, уберём узкое место 'fooP' в виде локальной переменной 'p', сделав вместо неё свойство 'this.p'

                                                            * foo: 2056ms
                                                            * bar: 2120ms

                                                            * fooP: 1528ms
                                                            * barP: 1564ms

                                                            К чести FF один в один.
                                                              0
                                                              так я и не спорю. просто tenshi писал, что:
                                                              исполняется код быстрее за счёт замыканий
                                                            0
                                                            ни рыба ни мясо:
                                                            function Baz()
                                                            {
                                                                var p = 0,
                                                                    innerBaz = function()
                                                                {
                                                                    this.a = 0;
                                                                };
                                                                
                                                                innerBaz.prototype.method = function() { this.anotherMethod(); };
                                                                innerBaz.prototype.anotherMethod = function() { this.a++; };
                                                                innerBaz.prototype.increaseP = function() { p++; };
                                                                innerBaz.prototype.getP = function() { return p; };
                                                                
                                                                return innerBaz;
                                                            };
                                                            
                                                            var baz = new (Baz());
                                                            


                                                            100000 итераций:
                                                            foo: 1.053ms
                                                            bar: 1.076ms
                                                            baz: 1.135ms
                                                            fooP: 1.674ms
                                                            barP: 0.793ms
                                                            bazP: 0.666ms
                                                            


                                                            1000000 итераций:
                                                            foo: 7.238ms
                                                            bar: 6.770ms
                                                            baz: 6.456ms
                                                            fooP: 5.181ms
                                                            barP: 6.334ms
                                                            bazP: 5.751ms 
                                                            


                                                            10000000 итераций:
                                                            foo: 28.549ms
                                                            bar: 65.462ms
                                                            baz: 62.685ms
                                                            fooP: 50.631ms
                                                            barP: 63.119ms
                                                            bazP: 56.899ms 
                                                            


                                                            А теперь бонус с заменой на «for (var i = 0; i < 1000000; i++ )»:
                                                            oo: 6.871ms
                                                            bar: 6.637ms
                                                            baz: 6.884ms
                                                            fooP: 7.131ms
                                                            barP: 3.517ms
                                                            bazP: 5.184ms 
                                                            

                                                            и при 10000000:
                                                            foo: 29.888ms
                                                            bar: 28.058ms
                                                            baz: 28.814ms
                                                            fooP: 42.857ms
                                                            barP: 24.987ms
                                                            bazP: 39.739ms 
                                                            


                                                            Вообще v8 всё это дело инлайнит, особенно если говорить о методах типа getP, насчёт медленней ли prototype — говорить вообще нельзя.
                                                  0
                                                  Единственный минус прототипного делегирования в том: если метод лежит слишком глубоко в цепочке прототипов, то время доступа к нему увеличивается (но не пропорционально количеству вложенности). В зависимости от браузера различия во времени доступа к первым 2-3м prototype не значительны, а дальше время доступа значительно увеличивается до нескольких десятков раз на каждый следующий.
                                                  Подробнее об этом рассказано(тесты с результатами) где-то тут: net.tutsplus.com/tutorials/javascript-ajax/extreme-javascript-performance/
                                                  Но часто ли вы видели 3+ вложенных прототипа?!
                                                  0
                                                  А как вы время компиляции узнаете во всех браузерах?
                                                    0
                                                    наверное, как и 85.6% всей статистики
                                                      0
                                                      примерно так:

                                                      var start= new Date
                                                      var proc= new Function('', Array( count ).join( source ) )
                                                      var end= new Date
                                                      var result= ( end — start.getTime() ) / count
                                                      0
                                                      то всегда можно приписать MyAnotherClass.prototype= MyClass.prototype

                                                      Кстати, нельзя.
                                                      Parent = function(){};
                                                      Child  = function(){};
                                                      Parent.prototype= Child.prototype;
                                                      
                                                      var parent = new Parent;
                                                      console.log(parent instanceof Child); // true
                                                      
                                                      +1
                                                      > Обёртки для создания классов: зло или добро?

                                                      1) Умеючи и для себя — добро.
                                                      2) Плохо сделанные монстры для неискушённой публики — зло.

                                                        0
                                                        MooTools.Class конечно не идеал, на самом деле он примешиват немного левоты (хотя особых минусов в этом нет):
                                                        newClass.$constructor = Class;
                                                        newClass.prototype.$constructor = newClass;
                                                        newClass.prototype.parent = parent;

                                                        по сравнению с Prototype.js в котором каждый метод оборачивается функцией MooTools.Class божественен.
                                                        В своих проектах я стараюсь не использовать классовой левоты. Использую стандартный подход ибо реальных преимуществ в использовании разных Class'ов я не вижу.
                                                        var Utils = {
                                                            inherit: function (child, parent) {
                                                                function F() {}
                                                                F.prototype = parent.prototype;
                                                                child.prototype = new F();
                                                                child.prototype.constructor = child;
                                                                child.superproto = parent.prototype;
                                                                return child;
                                                            }
                                                        };
                                                        
                                                        var Foo = function () {alert('Foo')};
                                                        
                                                        Foo.prototype.a = function () { alert('1') };
                                                        
                                                        var Bar = function () {alert('Bar')};
                                                        Utils.inherit(Bar, Foo);
                                                        
                                                        Bar.prototype.a = function () {
                                                            Bar.superproto.a.call(this);
                                                            alert('2');
                                                        };
                                                        
                                                        var bar = new Bar();
                                                        
                                                        bar.a();
                                                        

                                                        Иногда использую Utils.augment — mixin паттерн для особых нужд.
                                                          +1
                                                          Хорошую проблему поднял автор.

                                                          Почему я не люблю обертки типа $.extend() или New Class(). Потому, что в яваскрипте нет родного (нативного) объединения 2 хешей, и там оно, это объединение сделано дурацким неэффективным циклом for… in. Ну и плюс, надо минимизировать код, отправляемый клиенту, а значит проще написать function myClass(), myClass.prototype = {} и отказаться от всякой дури типа наследования, а не тащить по 100 Кб тяжелых библиотек, как это любят делать современные индусы от веба, чтобы в итоге написать

                                                          $('.showInfo').click(function(){ $('.info).toggle(); })

                                                          В общем, надо в первую очередь думать об макимально быстрой загрузке и выполнении кода, а не о дури типа многократного наследования, которое никому не нужно, иначе рискуете скатиться во что-нибудь типа Ext.JS который представляет из себя на редкость отвратительный пример того, что может получиться, когда люди увлекаются всякой объектно-ориентированной фигней, а в итоге выходит тормозящий 600 Кб набор библиотек.

                                                          О! Сейчас еще модно вообще огбходиться без объектов, ограничиваясь замыканиями, вроде

                                                          function initAll() {
                                                          var private1, private2, private3;
                                                          window.publicFunction = function() {… };
                                                          function private Function() {… }
                                                          }

                                                          Хотя. наверно к описываемой проблеме это никак не относится.
                                                            +3
                                                            Aux известный товарищ, который знает много страшных слов и любит примерять амплуа покрывателя икс-уями. Это не значит, что в пику ему стоит называть вещи не своими именами.

                                                            «Зло», как вы выразились, вовсе не в том, чтобы сокращать запись, а в том, чтобы привносить в JS понятия, которых в нём отродясь не было — классы! — и которые там совершенно не нужны. Потому что пользователи библиотек (Мутулз, кстати, моя любимая JS-либа) потом пытаются переносить туда же и логику чуждую языку. Приватные свойства, например. ЗАЧЕМ? Почему я двенадцать лет пишу на JS и мне ни разу не понадобились приватные свойства? Что я делаю неправильно?

                                                            Я к тому, что не нужно тащить в язык средства из чуждой ему парадигмы только для того, чтобы мимикрировать под привычную среду.
                                                              +2
                                                              Хрена себе… Такой флейм развели. Народ, окстись, единственная разница между прототипным и классовым языком это то, что у первого нету строгой разницы между описанием класса и экземпляром класса, а также отличается способ поиска перегруженных методов по иерархии наследования. всё.
                                                                +4
                                                                В ECMAScript нет (пока) сахара для классов. Но возможность классифицировать (т.е. способоность создавать объекты по заданному шаблону и с нужным классификационным тегом) там была всегда.

                                                                Основная разница классового и прототипного ООП (кто там жаловался на слово «идеология»? ;) именно идеологическая.

                                                                В одном случае — это классифицированное (или систематизированного) реюза (к примеру, построение линейной иерархии наследования, т.н. вертикальная «башня»), во втором — это неклассифицированное (или хаотичное) программирование («от кого хочу, от того код и „реюжу“).

                                                                С точки зрения цепи наследования большой разницы нет:

                                                                Прототипное: own -> proto -> proto -> proto -> null
                                                                Классовое (абстрактно): own -> class -> superclass -> superclass -> null.


                                                                При этом, в прототипном (делегирующем) программировании, цепь наследования так же линейна.

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

                                                                Наличие сущности „класс“ в языке не отменяет возможности классифицировать объекты. С этой точки зрения, например классы Python'a — это всего лишь синтаксический сахар того же делегирующего наследования, которое используется в ECMAScript. Для этого класс, естественно, должен быть сам первоклассным объектом. Здесь классы делятся на:

                                                                — Первоклассные динамические (first-class dynamic classes); примеры: Python, Ruby, CoffeeScript, ECMAScript (без сахара);

                                                                — Второклассные статические (second-class static classes); примеры: C++, Java.

                                                                Так вот, JS всегда имел „first-class dynamic classes“ в виде пары „конструктор + прототип“. Т.е. конструктор умеет генерировать объекты и задавать им классифицирующий тег (например, свойство constructor). При этом, как классифицировать объект — с сахаром ли (ключевое слово class) или без него — это уже второстепенный вопрос.

                                                                Самое интересное, что ECMAScript сам использует понятие „класса“ для классификации своих объектов. И, не удивительно, использует классифицирующий тег с таким же именем — [[Class]].

                                                                var a = {};
                                                                var b = [];
                                                                
                                                                // both are objects
                                                                alert([typeof a, typeof b]); // "object", "object"
                                                                
                                                                // but have different classification tags
                                                                var getClass = Object.prototype.toString;
                                                                
                                                                
                                                                alert([getClass.call(a), getClass.call(b)]); // "[object Object]", "[object Array]"


                                                                Поэтому, в JS есть (и всегда была) возможность классифицированного и неклассифицированного (прототипного) программирования. И каждое можно/нужно использовать, исходя из случая. А вот в классовой системе уже нельзя (в общем случае) программировать прототипно.

                                                                Если нужно просто повторно зареюзать код от другого объекта — зачем мне класс — используйте Object.create. Если я решил, что у меня должно быть несколько объектов одной классификации (например, компонент „кнопка“ в UI системе) — с какой стати мне отказываться от классов?

                                                                P.S.: недавно я отвечал в подобной теме:

                                                                www.mail-archive.com/jsmentors@googlegroups.com/msg00513.html
                                                                www.mail-archive.com/jsmentors@googlegroups.com/msg00503.html (примерное начало темы).

                                                                P.S.[2]:

                                                                — Есть ли в JS классы?

                                                                — Сахара нет, но классы (т.е. возможность классифицировать) — есть и всегда была. А сахар допиливается обёртками (так же, как и Python, так же, как и в CoffeeScript).

                                                                P.S.[3]: jashkenas.github.com/coffee-script/#classes
                                                                  0
                                                                  Чтобы не писать постоянно сноску «классы есть, но… без сахара», без сахара, без сахара… и ждать, пока запросят расшифровку на тему сахара, проще вспомнить, что «ECMAScript does not contain proper classes such as those in C++, Smalltalk, or Java» и отвечать жёстко, сверкая глазом и без всяких там условностей:

                                                                  — Есть ли в JS(ES3) классы?

                                                                  — Нет и никогда не было!
                                                                    0
                                                                    Классы не обязательно должны быть такие, как в Джаве. Но некоторые этого осознать не могут =)
                                                                      0
                                                                      Дайте определение тогда своё. Класс в js/es3 — это…
                                                                        0
                                                                        Функция и объект в одном лице
                                                                      +2
                                                                      Нет такого словосочетания «proper classes». Тот, кто написал это в ES3 спецификации имел в виду «second-class (static) classes». «Классы» в JS ничем не отличаются от классов в Python. И как они устроены изнутри, можно хорошо видеть на примере сахара CoffeeScript.

                                                                      Ещё раз — возможность классифицировать — вот что такое «классы». И у JS всегда была (и есть) эта возможность. Здесь можно реюзать код «хаотично» (прототипно) и систематизировано (классово). Всё остальное — это уже дополнительный идеологических сахар.

                                                                      Основное в ООП это:

                                                                      1. возможность генерировать объекты (т.е. всего лишь комплексные структуры с некоторым набором полей) — constructors.

                                                                      2. осуществлять выборку значений этих полей — selectors.

                                                                      Всё. Остальное: реюз, классификация, наследование, приватные свойства (для разделения уровня абстракций, но никак не для параноидального сокрытия) — это всё дополнительные сущности.

                                                                      Главное — это общие законы и методики. ECMAScript — это всего лишь конкретика, которая этим же (используемым в других языка) законам «подчиняется». Т.е. теоретические абстракции и идеология разработаны давно (и задолго до ES) — и все языки в той или иной мере просто воплощают эти абстрактные сущности для достижения главной цели программирования — борьбой со сложностью.

                                                                      Так что, в JS есть классы и всегда были. Но, да, без сахара ;) Хотя, конечно, эти размышления не для новичков.

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

                                                                      Я ж говорю, сам ES использует понятие класса для классификации объектов — свойство [[Class]], так почему же его бояться и относить к ненужному? Повторю, оно не нужно, если я хочу просто зареюзать код от другого объекта. А когда мне нужно классифицировать объекты — зачем мне отказываться от классов?

                                                                      P.S.: меня интересует знание вопроса из самой глубины, а не просто цитаты конкретных людей из TC-39, которые так же корректируют спецификации, ошибаются и т.д.
                                                                        0
                                                                        Дим, где в js возможность классифицировать??? Твои теги "[object Object]", "[object Array]" — это пшик, во-первых, этого вообще не было изначально, во-вторых никто строго не соблюдал до определённого момента (может и сейчас глюки, не веду статистику движков), я уж молчу про объекты хоста… Создал ты объект одним конструктором, потом вторым, третьим и что даёт общий для всех тег 'Object'? Классов нет. ;-)
                                                                          +2
                                                                          Язык программирования — для программиста. Необходимость в языке программирования и программировании в целом — это, повторю, борьба со сложностью. Чем более высоко абстрактен язык, тем эта борьба становится легче — путём человек-приближенных, высоко-абстрактных понятий ты описываешь ту или иную систему.

                                                                          Так вот, если, например, в биологии, для моего удобства, чтобы мне легче было «побороть некую хаотичность» я могу классифицировать объекты — т.е. группировать их по особому набору признаков; разделять их; видеть закономерности (опять же — набор свойств) у одного объекта, и видеть, что они отличаются у другого объекта, то почему бы мне этого не сделать в программировании?

                                                                          Опять же, ООП всего лишь теоретическая парадигма разработанная идеологами задолго до реализаторов движков и языков. Её удобно применять для решения конкретных задач — и именно где требуется (либо просто удобно) классификация структур по определенному признаку. В других задачах ООП может и не требоваться.

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

                                                                          В некоторых языках (Java, C++, др.) класс напрямую связан с понятием типа. Но в общем же случае, для того, чтобы иметь возможность классифицировать, нужен лишь классификационный тег (classification tag). Иногда называют тегом типизации (type tag) или, опять совсем просто — типом. Это некоторое свойство (или набор свойств в общем случае), которое позволяет однозначно определить объект твоей классификации от объекта с таким же набором свойств.

                                                                          Как я отметил выше, первая главная операция в ООП — это возможность порождать (construct) объекты. Если мы примем, что объект — это просто «набор свойств и поведений», то синтаксически мы можем прям так эти свойства со значениями и описывать:

                                                                          var point = {x: 1, y: 2};


                                                                          Если нам нужно три «точки», то:

                                                                          var a = {x: 1, y: 2};
                                                                          var b = {x: 3, y: 4};
                                                                          var c = {x: 5, y: 6};


                                                                          Но очевидно, что повторное использование кода (code reuse) методом «copy-paste» — это примитивная методика программирования (что если нам понадобится 100, 1000 точек?). Поэтому нам нужен некий удобный сахар для генерации объектов со схожей структурой. И самый простой способ сделать это — это использовать обычную функцию (да-да, точно так же, как мы всегда делаем, когда код повторяется больше двух раз — так называемое «правило двух» — «если код повторяется больше двух раз, вынеси (инкапсулируй) его в функцию».

                                                                          function makePoint(x, y) {
                                                                            return {
                                                                              x: x,
                                                                              y: y
                                                                            };
                                                                          }
                                                                          
                                                                          var a = makePoint(1, 2);
                                                                          var b = makePoint(3, 4);
                                                                          var c = makePoint(5, 6);


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

                                                                          Так вот, основная цель конструктора, как показано выше — это всего лишь удобный код реюз — генерация однотипных объектов.

                                                                          Но. Чтобы классифицировать твои объекты, недостаточно их просто генерировать. Нужен классифицирующий тег. Т.е. у собаки тоже 4 конечности, но она отличается от человека.

                                                                          Например, это может быть имя функции, которая создала объект. А лучше — ссылка на неё. Можно поместить этот тег прямо в самом объекте, но очевидно, что можно выделить вспомогательный спец. объект и задействовать делегацию — так, тег будет храниться в единственном экземпляре и будет доступен всем нашим объектам. Назвать его можно прямо и в лоб — constructor (вспомогательный объект при этом назовем prototype):

                                                                          makePoint.prototype = {
                                                                            constrcutor: makePoint
                                                                          };


                                                                          И модифицированный способ создания:

                                                                          function makePoint(x, y) {
                                                                            return {
                                                                              __proto__: makePoint.prototype,
                                                                              x: x,
                                                                              y: y
                                                                            };
                                                                          }


                                                                          Повторим, посредством делегации мы добьемся следующего эффекта:

                                                                          a.constructor // a.constructor -> not found, a.__proto__.constructor -> found == makePoint.


                                                                          Разработав всё это (нашу фабрику «makePoint» по генерации объектов-точек), мы придём к выводу, что некоторые вещи можно оптимизировать, убрать сложность (мы ж со сложностью боремся!):

                                                                          1. уберём (да, для сахара) этот префикс «make», заменим его ключевым словом «new»;

                                                                          2. сделаем return опциональным (правда, на кой ляд он нам в общем случае? — пусть система сама знает, что надо вернуть объект);

                                                                          3. будем создавать вспомогательный объект автоматически за кадром и устанавливать __proto__ тоже «по-тихому» (ты хочешь повторять это ручками каждый раз? — я нет);

                                                                          4. введём какое-нибудь специальное слово (например, this), чтобы ссылаться на созданный объект и не писать каждый раз эти скобочки { и }. Хотя, тут двояко, иной раз со скобками удобней.

                                                                          5. что-нибудь ещё придумаем…

                                                                          В итоге, мы получим систему конструкторов в JS:

                                                                          function Point() { // no "make"
                                                                            this.x = x;
                                                                            this.y = y;
                                                                          }
                                                                          
                                                                          var a = new Point(1, 2);
                                                                          var b = new Point(3, 4);
                                                                          var c = new Point(5, 6);
                                                                          
                                                                          a.constructor // Point


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

                                                                          Технические хаки (типа «я щас подменю свойство constructor» и т.д.) — меня, как программиста, вааапще не касаются. Удобно мне так классифицировать объекты — я классифицирую. Не буду я ничего ломать, подменять constructor, использовать поломанный [[Class]] в каком-нибудь движке и т.д.; я буду просто программировать в классифицирующем стиле. Если мне надо.

                                                                          Если не надо. Пожалуйста — полный арсенал утилит для бесклассового (хаотичного) реюза. Здесь уже никакой классифицирующий тег, естественно, не нужен. Здесь уже наоборот, другой объект, если он крякает, как мой, тоже (по идее!) пройти «тест на утку».

                                                                          В итоге, в JS (как и в любом другом языке с делегацией) можно программировать как с классами, так и без них. Но в классовой системе нельзя программировать в бесклассовом режиме.
                                                                            0
                                                                            Упрощу текст иначе субботний вечер меня просто не поймёт:

                                                                            — конструктор может создавать объекты;
                                                                            — а у объекта можно создать свойство, которое запомнит конструктор.

                                                                            В js есть классы, т.к. есть конструктор и можно свойства создавать, запомнив конструктор? Тогда я могу наварить спагетти в коде, но прекрасно себя при этом чувствовать, примитивно что-то там для себя классифицируя, связывая объекты друг с другом, свойство х, свойство y. Там фабрика, сям фабрика, вот и структура некая. В самом языке динамичность объектов, изменяемость со всех сторон, ноль жёстко заданных связей и поведения, делегирование, но по твоей логике это не мешает мне самому объявить «классы», иными словами в js есть классы, т.к. я «программирую в классифицирующем стиле». Стиль программирования решает. Точка. ;-)

                                                                            з.ы. Сlass is not equivalent to prototype/closure-based OOP in JS today. There is no way today to make an instance that is sealed against mutation by addition of new properties. /Brendan Eich
                                                                              +1
                                                                              > Стиль программирования решает.

                                                                              Однозначно.

                                                                              > ноль жёстко заданных связей и поведения

                                                                              Воот. Во всяком случая, я уже вижу, что ты подразумеваешь под словом «класс». И это именно то, о чём я упоминал выше — «second-class static classes».

                                                                              Однако, ты (да и Brendan) с какой-то стати отмёл «first-class dynamic classes».

                                                                              Я поясню терминологию, чтобы не было путаницы (не для тебя, а для тех, кто возможно не в курсе).

                                                                              — первоклассная (first-class) сущность — такая, которая может быть использована в качестве обычных данных (создана литерально в рантайме, передана в качестве аргумента функции, возвращена из функции и т.д.);

                                                                              — второклассная (second-class) сущность — соответственно, та, которая не может быть использована в качестве, описанном выше.

                                                                              Дальше. Динамически изменяемая (dynamic mutable) сущность — понятно, в рантайме расширяй объект сколько хошь. Static (или даже static immutable) — соответственно, наоборот.

                                                                              Так вот эти понятия с понятием классификации по большому счету вообще никак не связаны. По «менее большому счету» — мы можем лишь принять second-class static систему с возможностью генерировать объекты и классифицировать их. Конечно же, никакие фичи делегации, динамики и прочего из JS здесь не будут действовать.

                                                                              Но если мы возьмем делегирующую first-class dynamic систему с «сахаром», то мы тоже получим понятие класса. При этом мы свободно сможем дописывать в рантайме новые слоты, добавлять новые методы, менять класс/прототип объекта динамически, передавать класс в качестве аргумента и т.д. dmitrysoshnikov.com/ecmascript/ru-chapter-7-1-oop-general-theory/#dinamicheskaya-klassovaya-organizatsiya

                                                                              Так вот, почему, если Python (или CoffeeScript) делегирующие языки, их называют классовыми? Потому что в них всего лишь по-дефолту, «из коробки» уже написан этот сахар, эта обёртка с использованием ключевого слова class. Но внутри-то они — абсолютно те же, что и JS.

                                                                              Еще раз, все знают, что JS — прототипный язык (ладно, я тоже, допустим, знаю). Но при этом в нём, как и в любом делегирующем языке, можно программировать в классовом стиле. И да, именно стиль решает. А точнее не стиль, а понятие «возможность генерировать и классифицировать» — вот, что называется в абстракции «класс». А конкретные реализации — это уже дело десятое. web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html
                                                                              0
                                                                              м… ты тоже перешёл на тёмную сторону силы? %-)
                                                                            0
                                                                            Кстати, может тогда ты напишешь. Класс в js/es3 — это…
                                                                              0
                                                                              … связка «конструктор + протоип» с классифицирующим стилем программирования.
                                                                                0
                                                                                А без «стиля» классов нет в языке? ок… ;-)
                                                                                  0
                                                                                  без стиля их может не быть в программном продукте
                                                                                    0
                                                                                    В языке нет «second-class static classes» (по твоему определению — «ноль жёстко заданных связей и поведения»). Естественно, и очевидно, что таких классов нет и не было (ну а как? — везде динамика и всё «first-class» — это «физически» невозможно). Здесь всё верно.

                                                                                    А вот, ещё раз, «first-class dynamic classes» — есть и всегда были. Только без сахара.

                                                                                    Здесь, либо мы тогда Python и CoffeeScript должны называть только делегирующе-прототипными, либо, всё-таки (и правильно) — допускать, что в таком же (делегирующе-прототипном) языке мы можем выделить понятие «класса» как (повторю) «возможность генерировать и классифицировать».

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

                                                                                    Перестала нравится «копи-паста» при генерации однотипных объектов — всё, убрали (инкапсулировали) её в функцию; избавились от этой «сложности».

                                                                                    Хочется классифицировать по тегу, но не хочется занимать много памяти, храня одну и ту же запись в самом объекте — всё, сделали вспомогательный спец.объект-прототип и задействовали делегацию (с виду вроде свойство «родное», а на самом деле хранится в одном месте). От этой «сложности» тоже избавились.

                                                                                    Надоело каждый раз вручную ставить прототип (и все остальные приблуды, типа return'a и т.д.)? — всё, сделали «за кадром» это действие.

                                                                                    Т.е., видишь, да — самые обыкновенные практические задачи-причины.

                                                                                    В итоге, можно остановиться на каком-то определённом этапе. А те, кто увидят ещё какую-то сложность, могут дальше от неё избавиться. Захотелось обособить функцию классифицирующей генерации от обычной — всё, заменили слово «function» на «class» в грамматике. Но раскладывается-то («десахарится», desugars) оно внутри всё равно на эту же самую «function» — jashkenas.github.com/coffee-script/#classes

                                                                                    И т.д., и т.п.

                                                                                    Не захотели делать mutable объекты — пожалуйста, заморозь (freeze) их. Кстати, если мы избавимся от динамики и изменяемости — мы подпадём под твоё определение «класса» с «ноль жёстко заданных связей и поведения»? Кстати, цитата Brendan'a, вырванная из контекста, тоже говорит лишь об изменяемости, т.е. о «second-class static classes», которых, естественно, не было в ES3. Но в ES5 с Object.freeze — пожалуйста, убери изменяемость, если хочется.

                                                                                    После заморозки — не захотели делать всё это first-class, пожалуйста, сделай second-class (не будут у тебя классы объектами, а просто — «статические лепёшки» в коде, которые могут генерировать и классифицировать объекты — точно так же, как могли до этого — обычные «first-class dynamic functions»).

                                                                                    Т.е. разницы нет как классифицировать — с ключевым ли словом «function» или с ключевым словом «class». Главное — это сами причины и нужды и, как следствие, методика, стилистика. А синтаксис — это уже даже не десятое, а сотое дело.

                                                                                    P.S.: в самом «низу», на уровне ячеек памяти, в абстракции, она тоже классифицирована (типизирована). Как, думаешь, различается указатель на int или указательно на float. Или, что, например, этот блок памяти может хранить int — ведь ячейки «все равны»? Точно так же — по классификационному тегу. Есть ячейки данных, и есть специальная ячейка, которая хранит тип/класс того, что в неё кладут — для проверки.

                                                                                    P.S.[2]: Ещё раз — разница между «классовое» и «прототипное» только идеологическаяклассифицированный реюз и генерация и неклассифицированный (хаотичный, прототипный) реюз и генерация. Технически же, это может быть одна и та же система.
                                                                                      0
                                                                                      >… таких классов нет и не было. Здесь всё верно.

                                                                                      Смотри, как всё просто — в языке нет классов, нет жёстких связей и структур, заданных классом, нет вообще классовой терминологии (а те три терминологических грамма, что всё-таки насыпались в доки: new, instanceOf… самим же создателем признаются неточными по сути языка, извиняется он).

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

                                                                                      Ты же предлагаешь мять в руках изощрённый терминологический пластилин: вот таких классов нет, но есть такие..., классы есть, но нет сахара..., тут вроде так, но если поставить тег, то будет эдак, тут подходит термин как Python-е, тут борьба со сложностью. В принципе, плевать, что и как в Python-е и тем более в CoffeeScript-е, да мало ли сколько существует жёванных-пережёванных определений для классов и вообще для всего. Зачем javascript-у цепляться за термин «first-class dynamic classes», что это даст изучающему язык? Ничего, кроме мороки, ни в доках нет, ни в сети язык в этом контексте не обсуждается, никакого практического приложения. Вон какие простыни приходится тут писать вокруг одного предложения: «классы в языке — это возможность генерировать и классифицировать»… ну, тогда везде они… классы эти.

                                                                                      зы. всё-таки продолжаю бороться со сложностью — классов в js нет ;-)

                                                                                        0
                                                                                        Всё, я понял, где у нас «нестыковка» (ну, образно — на самом деле, и ты, и я правы).

                                                                                        Ты смотришь на это со стороны обособления и четкого разграничения (кстати, заметь, как бы забавно и странно не звучало, ты тоже именно классифицируешь, стратифицируешь JS от других). Тебя интересует, а как преподать это в большей мере новичкам, показать им эти «разграничительные классифицирующие теги» aka «смотрите, мой язык совершенно другой: тут нет того, тут нет сего».

                                                                                        Я же, действительно, размышляю об общих структурах и закономерностях, которые прослеживаются во всех языках. Я действительно размываю эти рамки, потому что вижу в них только конкретику отдельно взятой реализации; реализации общих теоретических схем, ещё раз повторю, разработанных идеологами безотносительно каких-то там ECMAScript'ом, Jav(a), C, CoffeeScript'ов, Python'ов и т.д.

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

                                                                                        показать его особенности, его своеобразие, его красоту

                                                                                        Воот. Вот именно здесь, я понимаю, «немного печально», «что мой язык кем-то вдруг размывается и смешивается с »немоими" языками".

                                                                                        Да, всё верно. Стояла бы задача «выделить терминологические и идеологически различия и рассказать о них новичкам» — вперёд, никто бы не стал ничего «размывать».

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

                                                                                        Поэтому да, если хочешь показать именно обособленности новичкам — конечно удобно выделять их. Если мы беседуем в рамках теории языков программирования, их семантики и дизайна — есть «second-class static classes» и есть «first-class dynamic classes» (с возможными вариациями, например, «first-class static classes» — которые «замороженные»), и каждая технология, стратегия может быть использована в отдельно взятом конкретном языке.

                                                                                        «классы в языке — это возможность генерировать и классифицировать»… ну, тогда везде они… классы эти.

                                                                                        Да. Как бы утрированно не звучало — да, именно. Если нужно классифицировать. Повторю, если не нужно — можно использовать бесклассовый (прототипный) реюз и генерацию.

                                                                                        P.S.:

                                                                                        всё-таки продолжаю бороться со сложностью — классов в js нет ;-)

                                                                                        Ну, либо так — тоже избавление от сложности. Только с альтернативной точки обзора ;)
                                                                                          0
                                                                                          У вас отличные статьи на сайте, спасибо за труд! И за развернутые комментарии здесь!

                                                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                        Самое читаемое