Javascript наследование для чайников

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

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


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


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

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

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

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

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

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


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

    function inherit_A(Child, Parent)
    {
        var F = function () { };
        F.prototype = Parent.prototype;
    
        Child.prototype = new F();
        Child.prototype.constructor = Child;
        Child.super = Parent.prototype;
    }
    

    И его даже можно использовать:

    $(document).ready(function ()
    {
        function Man(name) { this.name = name }
        Man.prototype.say = function () { console.log("My name is " + this.name) }
    
        function Gentleman(name) { Man.call(this, name); }
        inherit_A(Gentleman, Man);
    });
    

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

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

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

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


    Вот и он:

    function inherit_B(Child, Parent)
    {
        var F = function () { };
        F.prototype = Parent.prototype;
        var f = new F();
        
        for (var prop in Child.prototype) f[prop] = Child.prototype[prop];
        Child.prototype = f;
        Child.prototype.super = Parent.prototype;
    }
    

    Он просто берет, и копирует все свойства из прототипа родителя в прототип наследника, так что теперь мой любимый объектный литерал работает как надо:

    $(document).ready(function ()
    {
    
        function Man(name) { this.name = name }
        Man.prototype =
        {
            constructor: Man,
            THOUGHTS: "wanna beer!",
            say: function ()
            {
                console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'")
            }
        }
    
        function Gentleman(name, prefered_beverage)
        {
            Man.call(this, name);
            this.prefered_beverage = prefered_beverage;
        }
        Gentleman.prototype = { constructor: Gentleman, THOUGHTS: "it's teatime!" }
        inherit_B(Gentleman, Man)
    
        function Programmer(name, prefered_lang)
        {
            Gentleman.call(this, name, "Cofee");
            this.prefered_lang = prefered_lang;
        }
        Programmer.prototype =
        {
            constructor: Programmer,
            THOUGHTS: "runtime error 138? wanna debug XD!"
        }
        inherit_B(Programmer, Gentleman)
    
        var man = new Man("Jack");
        var gentleman = new Gentleman("John", "Orange pekoe");
        var programmer = new Programmer("James", "C++");
    
        man.say();
        gentleman.say();
        programmer.say();
    });
    

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

    sample.js:11 My name is Jack and i think:'wanna beer!'
    sample.js:11 My name is John and i think:'it's teatime!'
    sample.js:11 My name is James and i think:'runtime error 138? wanna debug!'
    

    Отлично. А теперь представим гипотетическую ситуацию, когда родительских классов много, и нужно, например, вызвать какой-нибудь метод, который был перегружен 2-3 класса назад. В такой ситуации, конечно, лучше еще раз хорошенько присмотреться к архитектуре своего приложения. Теперь допустим, что там все работает, как задумано, тогда чертовски неудобно писать, например, так:

    this.super.super.super.someMethod.apply(this)

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


    function inherit_C(Child, Parent)
    {
        var F = function () { };
        F.prototype = Parent.prototype;
        var f = new F();
    
        for (var prop in Child.prototype) f[prop] = Child.prototype[prop];
        Child.prototype = f;
        Child.prototype[Parent.prototype.__class_name] = Parent.prototype;
    }
    

    Эта функция в прототип потомка добавляет объект с ссылкой на прототип родителя. Чтобы она заработала как надо, в прототип каждого класса нужно добавить поле

    __class_name 

    Вот, например, предположим, что в нашей иерархии появился класс BadProgrammer, которому как раз понадобился удобный доступ к прототипу далекого предка. Немного модифицируем предыдущий пример:

    $(document).ready(function ()
    {
         function Man(name) { this.name = name }
        Man.prototype =
        {
            __class_name: "Man",
            constructor: Man,
    
            THOUGHTS: "wanna beer!",
            say: function ()
            {
                console.log("My name is " + this.name + " and i think:'" + this.THOUGHTS + "'")
            }
        }
    
        function Gentleman(name, prefered_beverage)
        {
            Man.call(this, name);
            this.prefered_beverage = prefered_beverage;
        }
        Gentleman.prototype =
        {
            __class_name: "Gentleman",
            constructor: Gentleman,
            
            THOUGHTS: "it's teatime!"
        }
        inherit_C(Gentleman, Man)
    
        function Programmer(name, prefered_lang)
        {
            Gentleman.call(this, name, "Cofee");
            this.prefered_lang = prefered_lang;
        }
        Programmer.prototype =
        {
            __class_name: "Programmer",
            constructor: Programmer,
    
            THOUGHTS: "runtime error 138? wanna debug XD!"
        }
        inherit_C(Programmer, Gentleman)
    
        function BadProgrammer(name)
        {
            Programmer.call(this, name, "brainfuck");
        }
        BadProgrammer.prototype =
        {
            __class_name: "BadProgrammer",
            constructor: BadProgrammer,
    
            THOUGHTS: "runtime error 138? wanna debug XD!",
            say: function () { this.THOUGHTS = this.Man.THOUGHTS; this.Man.say.apply(this); }
        }
        inherit_C(BadProgrammer, Programmer)
    
        var man = new Man("Jack");
        var gentleman = new Gentleman("John", "Orange pekoe");
        var programmer = new Programmer("James", "C++");
        var badprogrammer = new BadProgrammer("Jake");
    
        man.say();
        gentleman.say();
        programmer.say();
        badprogrammer.say();
    });
    

    Класс BadProgrammer воспользовался мыслями самого первого звена нашей эволюционной цепочки классов, и, теперь, думает совсем не о том, о чём обычно думают программисты ;)

    My name is Jack and i think:'wanna beer!'
    My name is John and i think:'it's teatime!'
    My name is James and i think:'runtime error 138? wanna debug!'
    My name is Jake and i think:'wanna beer!'
    

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


    Не представляю в какиx случаях это может пригодится, но, возможно, когда-нибудь, кому-нибудь может понадобится множественное наследование. Его тоже вполне можно реализовать:

    function inhertit_multiple(child)
    {
        for( var i = 1; i < arguments.length; ++i )
        {
           var parent = arguments[i]
           for (var prop in parent.prototype)
           {
               if (!child.prototype[prop]) child.prototype[prop] = parent.prototype[prop];
           }
           child.prototype[parent.prototype.__class_name] = parent.prototype;
        }
    }
    

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

    $(document).ready(function ()
    {
        function Mammy() { this.mammy_message = "You Dont love me!" }
        Mammy.prototype =
        {
            __class_name: "Mammy",
            say_something_wise: function () { console.log(this.mammy_message) }
        }
    
        function Daddy() { this.daddy_message = "I just don't want to be a dad!" }
        Daddy.prototype =
        {
            __class_name: "Daddy",
            say_something_wise: function () { console.log(this.daddy_message) }
        }
    
        function Lad()
        {
            this.lad_message = "And i want a candy!";
            Mammy.apply(this);
            Daddy.apply(this);
        }
        Lad.prototype =
        {
            __class_name: "Lad",
            say_something_wise: function ()
            {
                this.Daddy.say_something_wise.call(this);
                this.Mammy.say_something_wise.call(this);
                console.log(this.lad_message);
            }
        }
        inhertit_multiple(Lad, Mammy, Daddy)
    
    
        var lad = new Lad();
        lad.say_something_wise();
    });
    

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

    I just don't want to be a dad!
    You Dont love me!
    And i want candy!
    


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

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

    Похожие публикации

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

      +5
      Из пункта 2:
      > или мои функции полностью затрут унаследованный прототип, или…
      --проблема решается, если свойства своего прототипа подмешать в созданный функцией inherit_A() прототип наследника:

      extend(Child.prototype, {ваши свойства в объектной нотации});

      Чего строго нельзя сделать, не потеряв свойств унаследованного класса — подмешать в другую сторону — в ваш хеш пытаться загрузить свойства выражения (new F()). Мана, приобретённая магией new, растеряется по пути (в процессе).
        +5
        спасибо, благодаря вам я стал немного умнее :)
        0
        Ну тогда зацените и мой велосипед с более прозрачной архитектурой и множественным наследованием без сторонних функций. Не забудьте почитать комментарии.
        А вообще, в образовательных целях велосипеды весьма полезны ;)
          –1
          Простите, но, на мой взгляд, насчет прозрачности это вы погорячились. Хотя я сейчас недостаточно адекватен так что лучше на мое мнение сейчас внимания необращать :)
          +3
          Стоян Стефанов / Паттерны программирования — в этой книге можно почитать о классическом наследовании в javascript.
            0
            Спасибо, обязательно её найду.
              0
              Сначала так и подумал, что автор импровизирует на тему этой книги…
              +18
              Слушайте, когда-нибудь уже переведутся желающие улучшить наследование в JavaSсript? Неужели так сложно просто смириться с тем, что прототипная модель не лучше и не хуже классовой — она просто другая?
                0
                Уж лучше что-то делать, тем самым учиться, наступать на свои грабли, тем самым понимаю тонкости языка, се умеют пользоваться jQuery, но мало кто знает что внутри него.
                  +4
                  У меня есть некоторые сомнения, что «изобретать костыли для своего собственного неумения пользоваться прототипной моделью» = «понимать тонкости языка». Скорее, у меня есть уверенность в обратном.
                  –12
                  > что прототипная модель не лучше и не хуже классовой — она просто другая

                  Есть мнение, что таки хуже. И посты аналогичные обсуждаемому — тому подтверждение. Но да, остаётся смиряться, в ожидании Dart.
                    +4
                    Есть мнение, что слишком большому числу программистов банально лень осваивать новые концепции, проще костылей понапридумывать.
                      –1
                      Они все тоже изобретают костыли от неумения использовать прототипную модель?

                      habrahabr.ru/blogs/programming/130064/#comment_4312350
                        +2
                        Я подробно разбирал этот момент.
                        habrahabr.ru/blogs/javascript/130589/
                          –4
                          > На мой взгляд именно из-за нежелания принять парадигму JS и проистекает желание сделать над ним метанадстройку.

                          Отлично. Но как Вы думаете, чем, все-таки, обусловлено это такое синхронное нежелание тех, кто с таким завидным постоянством городит «костыли» (см. примеры из habrahabr.ru/blogs/programming/130064/#comment_4312350)?
                            0
                            Вы по-моему только что процитировали мой ответ на Ваш вопрос.
                              0
                              Ок, я понял Ваш аргумент, который заключается в том, что (цитирую)

                              некая инициативная группа в составе гугла просто не понимает прототипную парадигму и стремится навязать взамен классовую


                              У меня же мнение кардинально другое. Классическое ООП проще (в хорошем смысле) прототипного. Под хорошим смыслом я понимаю то, что оно позволяет сделать то же самое не менее выразительно, но обходясь меньшим базисом примитивов. А чем меньше базис, тем лучше, Вы это сами признали, сказав:

                              не надо без нужды увеличивать энтропию


                                0
                                Вы ожидаете, что я встряну в этот холивар? Зачем?
                                Тут давно уже не просто миллион копий сломан, а прямо научные трактаты написаны:
                                Classes vs. Prototypes. Some Philosophical and Historical Observations
                                  +2
                                  Чем дальше я юзаю классовые оболочки для JS (а я их использовал много разных) — тем больше я понимаю, что оболочка должна укорачивать запись, но не больше. Никаких быстрых ссылок на родителя, никаких клонирований объекта. К сожалению, очень усложняется дебаг, понимание программы и скорость выполнения.

                                  Не вижу ничего плохого в такой записи:
                                  var Foo = new Constructor( Bar, {
                                      constructor: function () {
                                          Bar.call( this );
                                      },
                                      fooMethod: function () {
                                        Bar.prototype.barMethod.call( this );
                                      }
                                  });
                                  


                                  Она всего-лишь сокращает запись стандартных практик. Всё остальное — от лукавого.
                                    0
                                    не есть гут вызывать методы родителя из родителя.
                                    Foo.superclass.barMethod.call(this);
                                    хотя в данном случае this.barMethod() — он только в родителе и определен.
                                      0
                                      Ну про this.barMethod — естественно.
                                      Foo.superclass.barMethod.call(this); не очень работает при использовании mixin.
                                        0
                                        а тебя никто не заставляет в его супер классе хранить реального родителя.
                                        У меня, например, при необходимости вначале создаётся «коллективный» предок, а потом уже от него наследуется.
                                        Зато все работает прозрачно.
                                          +1
                                          Вот тут то и начинаются трудности в отладке. Плюс IDEA может помочь перейти по клику на метод предка.
                                            +1
                                            вот чего нет так это трудностей :)
                                            А на мотание\автокомплит по js коду я уже давно положил болт — нигде оно нормально и внятно не работает.
                                            Тем более если классы создаются и наследуются по строковым идентификаторам.
                                              0
                                              могу посоветовать вам поизучать JSDoc =) IDEA его отлично поддерживает.
                                    0
                                    > Вы ожидаете, что я встряну в этот холивар? Зачем?

                                    Начнем с того, что Вы уже встряли в него.

                                    > Тут давно уже не просто миллион копий сломан, а прямо научные трактаты написаны:
                                    Classes vs. Prototypes. Some Philosophical and Historical Observations


                                    Копий сломано да, но то ли еще будет.
                                    Трактат хороший, да. Но, во первых, философия (из трактата) и практика — «две большие разницы». А по части практики (точнее, практичности) этот трактат вряд-ли актуален, т.к. написан лишь год спустя создания JS, принесшего идею прототипов в широкие массы, когда все плюсы и минусы этого подхода были еще не вполне исследованы на практике. А вот дальнейшая практика как раз показала, что более прагматичным является подход классического ООП.
                                      0
                                      > Начнем с того, что Вы уже встряли в него.

                                      Ничего подобного. Я просто декларирую: прототипный подход не лучше и не хуже классового. Точка.

                                      > А вот дальнейшая практика как раз показала, что более прагматичным является подход классического ООП.

                                      Дальнейшая практика показала только то, что при тотальном преподавании в вузах классового ООП освоение каких-либо других концепций становится абсолютно непреодолимым барьером для 90% выпускников этих вузов. То же, впрочем, верно и в отношении, например, функционального программирования.

                                      Что касается практики, то совершенно же очевидно, что в браузере 90% задач — разовые, нетипизированные, когда требуются манипуляции с существующими в единственном числе программными объектами. Разумеется, js — лучшее из существующий решений для работы с такими классами задач.
                                        0
                                        > Ничего подобного. Я просто декларирую: прототипный подход не лучше и не хуже классового. Точка.

                                        Угу, а я просто декларирую, что хуже. В этом и есть смысл нашего спора в этом обсуждении, или, другими словами холивара.

                                        > некая инициативная группа в составе гугла просто не понимает прототипную парадигму и стремится навязать взамен классовую

                                        Вы правда в это верите?

                                        > Дальнейшая практика показала только то, что при тотальном преподавании в вузах классового ООП освоение каких-либо других концепций становится абсолютно непреодолимым барьером для 90% выпускников этих вузов.

                                        Дело не в сложности концепции прототипов. Из приведенного Вами же трактата:

                                        > When compared to class-based
                                        languages, prototype-based languages are conceptually simpler, ...


                                        А дело в том, что она попросту неудобна. Хочешь — не хочешь, но при усложнении системы удобнее думать и строить её в терминах классов, а их в JS-то и нету, приходится изобретать, что и демонстрирует обсуждаемая статья и сотни ей подобных. Вы же утверждаете что все кто так делают просто «дураки и не лечатся» (не буквально :-) ).

                                        Ну в целом, пожалуй, надеюсь, моя аргументация осталась понятна Вам, а Ваша, кажется, мне.

                                          0
                                          > Угу, а я просто декларирую, что хуже. В этом и есть смысл нашего спора в этом обсуждении, или, другими словами холивара.

                                          Вряд ли — я не собираюсь с Вами спорить.

                                          > Вы правда в это верите?

                                          Там по-моему в топике исчерпывающе описано моё мнение относительно гуглёвой инициативы.

                                          > А дело в том, что она попросту неудобна.

                                          Вам неудобна, мне удобна.

                                          > Хочешь — не хочешь, но при усложнении системы удобнее думать и строить её в терминах классов, а их в JS-то и нету.

                                          Неправда. При усложнении системы удобнее думать в терминах объектов и отношений между ними (см. ООП). Они в js есть. Думать в терминах объектов можно классами, а можно прототипами. А вот с этим уже проблемы.
                                            0
                                            > Вряд ли — я не собираюсь с Вами спорить.

                                            Но Вы уже поспорили, самим фактом ответа.

                                            > Ну в целом, пожалуй, надеюсь, моя аргументация осталась понятна Вам, а Ваша, кажется, мне.

                                            Согласен, что спор можно прекращать, по причине того, что аргументы оппонентов стали ясны друг другу, несмотря на несогласие с ними.
                        +1
                        через прототипное наследование нативно и в два щелчка реализуется АОП — обертками вокруг оригинальных методов прототипа. Как это нативно реализовать в Dart или существующих языках с «классовым» наследованием?
                          +3
                          Да пожалуйста.

                          >>> def p(s): print s
                          
                          >>> def aop(pre_f, post_f):
                          	def decorate(f):
                          		def decorated(*args):
                          			pre_f(*args)
                          			f(*args)
                          			post_f(*args)
                          		return decorated
                          	return decorate
                          
                          >>> @aop(lambda : p('before'), lambda : p('after'))
                          def f():
                          	print 'f'
                          
                          	
                          >>> f()
                          before
                          f
                          after
                          >>> def print_field(self, msg):
                          	print msg, self.field
                          
                          	
                          >>> class A:
                          	@aop(lambda self: print_field(self,'Field before method:'), lambda self: print_field(self, 'Field after method:'))
                          	def method(self):
                          		self.field+=2
                          
                          		
                          >>> a = A()
                          >>> a.field = 5
                          >>> a.method()
                          Field before method: 5
                          Field after method: 7
                          
                            0
                            Это хорошо, но не АОП: АОП необходимо для разделения «основной» и «дополнительной» логики — так, чтобы основная о дополнительной ничего не знала и не зависела от нее. То есть, например, тестирование основной логики не включало в себя тестирование дополнительной. Соответственно, чтобы дополнительную логику можно было добавить задним числом, отдельно от основной.

                            В JavaScript это реализуется легко изменением прототипа. В классовом же языке нельзя изменить класс после его объявления, поэтому создаются хаки, завязанные на изменение полученного низкоуровневого кода. В интерпретируемых языках типа Ruby с этим получше — тоже через reflection, но изменяется сам исходный код, правда, через «костыли» типа eval.
                              0
                              > В классовом же языке нельзя изменить класс после его объявления, поэтому создаются хаки, завязанные на изменение полученного низкоуровневого кода. В интерпретируемых языках типа Ruby с этим получше — тоже через reflection, но изменяется сам исходный код, правда, через «костыли» типа eval.

                              Что-то Вы опять ошибаетесь.

                              >>> class A:
                              	def method(self):
                              		self.field+=2
                              
                              		
                              >>> a = A()
                              >>> a.field = 5
                              >>> a.method()
                              >>> def p(s): print s
                              
                              >>> def aop(pre_f, post_f):
                              	def decorate(f):
                              		def decorated(*args):
                              			pre_f(*args)
                              			f(*args)
                              			post_f(*args)
                              		return decorated
                              	return decorate
                              
                              >>> def print_field(self, msg):
                              	print msg, self.field
                              
                              >>> A.method = aop(lambda self: print_field(self,'Field before method:'), lambda self: print_field(self, 'Field after method:'))(A.method)
                              >>> a.method()
                              Field before method: 7
                              Field after method: 9
                              
                                +1
                                Ошибка тут будет в том, чтобы считать Python классовым языком — скорее, он прототипный, хотя и нет возни с prototype, которой знаменит JS:

                                Сравним Python с JavaScript:
                                1) в обоих языках с «классом» можно обращаться, как с объектом, в тч менять его структуру (да и называются классы Python «Class objects»)

                                2) в обоих языках в экземпляр класса содержимое класса не копируется — при изменении структуры класса изменится и объект, но доступно, будто бы есть напрямую в объекте

                                3) в обоих языках можно изменить структуру самого объекта, причем как пустого, так и созданного как экземпляр класса

                                4) в Python нельзя произвольный объект использовать как класс/прототип класса; в JavaScript можно, но по факту так не делают — как классы/прототипы используют только однотипные функции-конструкторы и их свойство prototype; то есть практические принципы использования одинаковы

                                5) в JavaScript для различения статических/нестатических методов (то есть методов класса/экземпляра) используется свойство prototype у функции-«класса», в Python — используется передача/непередача self в метод и дополнительные извращения либо аннотация @staticMethod.

                                То есть по сути практическое поведение классов в обоих языках — одинаковое, разница — во внутренних деталях реализации.

                                То есть:
                                1) Python, по сути, ближе к прототипному языку, чем к классовому, и имеет тот же характерный геморрой со «статическими» методами

                                2) отличает Python от «чистого» прототипного языка только невозможность использовать любой объект как прототип — на самом деле, это и в JavaScript не используется, а, например, CoffeeScript явно от этого ушел, хоть и не запретил

                                Поэтому Python в практических целях вполне можно считать прототипным языком. И реальная работа со статическими методами там ничуть не проще аналогичной в Javascript, для которой по сути и сделаны prototype/__proto__
                                  0
                                  Ближе — не ближе — это уже досужие домыслы. Факт же в том, что в питоне есть классы из коробки, а в JS — нету. И, потому, в JS все городят свою реализацию классов. И при этом нас упорно убеждают что в этом нет никакой проблемы.
                                  В питоне действительно нет. Никому не придет в голосу изобретать своё ООП. Ладно, вру, придет. Но уж точно не придет применять это в реальном проекте, просто потому что незачем.

                                  Ниже Вы писали

                                  > Кстати, вот отличная демонстрация «чистого» АОП в Javascript — habrahabr.ru/blogs/webdev/131690/. Понятно, что можно навешивать и на конкретный объект, и на прототип. Python так уже не сможет.

                                  Все-таки признайте уж свою ошибку.
                                    +1
                                    Ошибку признаю без проблем — да, я ошибся, заявляя, что Python так не сможет, и спасибо Вам, что показали обратное )

                                    Честно говоря, ранее специально изучал возможности AOP в Python, не зная толком самого Python — так вот найденные решения были хуже — в основном, через аннотации. А то, что Вы показали, нравится гораздо больше.

                                    Javascript: да, классов «из коробки» нет, а в Python нет static-методов из коробки. В обоих случаях есть более одного варианта решения. Спор-то изначальный был про то, что лучше — прототипы или классы.

                                    Про Python так же можно сказать, что он — прототипный язык с реализацией прототипов «из коробки»
                                      0
                                      > Ошибка тут будет в том, чтобы считать Python классовым языком — скорее, он прототипный

                                      Абсолютно верно, Python — прототипный язык с классам в виде «сахара». Такие же классы в CoffeeScript. Такие же планируются (и сейчас активно обсуждаются в es-discuss) в следующей версии JS.
                                        0
                                        > Абсолютно верно, Python — прототипный язык с классам в виде «сахара».

                                        Да ладно? И как же на питоне будет выглядеть создание нового объекта на основе прототипа из существующего объекта?
                                          0
                                          > Да ладно?

                                          Ага. Конкретика имеет меньшее значение, чем одинаковые методики разрешения имет свойств/методов и динамика в целом. Естественно, относительно конкретики, есть отличия. Но основа та же делегация.

                                          Другой пример, как уже было отмечено — CoffeeScript. На своем уровне абстракции и в своей семантике он классовый (точнее, классово-прототипный). Но как и в Python, классы там всего лишь сахар над «делегацией».

                                          P.S.: и Ruby такой же. И Lua.
                                0
                                Кстати, вот отличная демонстрация «чистого» АОП в Javascript — habrahabr.ru/blogs/webdev/131690/. Понятно, что можно навешивать и на конкретный объект, и на прототип. Python так уже не сможет.
                          • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              Раз уж Вы написали:

                              1) Слайд 21: так и не понятно, почему это плохо. Да, можно в функцию втиснуть, можно CoffeeScript использовать

                              2) Слайд 27: у Вас изменилась модель предметной области, то есть реальная иерархия — логично, что нужно изменить иерархию в программе

                              3) Заметьте, у Вас иерархия изначально некорректно выстроена: практически все пересекаются друг с другом, например, «грызуны» и «живут в воде»

                              4) Слайд 38 и дальше: Сначала Вы говорили о клонировании экземпляров, (Вася -> Петя), но здесь показываете клонирование типов (cat -> octocat).

                              5) Слайд 38 и дальше — Вы в прототип «octocat» засовываете созданный объект. Даже если конструктор ничего не делает (на что полагаться нельзя), это некрасиво. А если он принимает хоть какие-то параметры?

                              На этом сдался
                              • НЛО прилетело и опубликовало эту надпись здесь
                              +1
                              Сложно.
                              Иначе бы не было кучи фрэймворков так или иначе реализующих «классовость» в яваскрипте.
                              В мире миллионы «классовых» программистов, и дешевле «улучшить наследование», чем обучить их новому стилю программирования.
                                +1
                                Простите, Вы операционной системой какой пользуетесь?
                                Наверное, Вы тоже «за» обучение всех работе в винде — это же дешевле, чем обучить работе в Linux.
                                  +2
                                  Я пользуюсь Mac OS X, но вы правы — для бизнеса винда выходит обычно дешевле линукса, так как а) виндой все умеют пользоваться, б) под винду есть весь необходимый софт, в) винда работает даже на железе десятилетней давности (можно, конечно, найти старинный дистрибутив линукса для старинных компов, но на нём не заработает современный софт — например, текущая версия Firefox не ставится на старинный OpenSUSE, и поэтому у нас до сих пор Firefox 2 на многих линуксовых рабочих местах).
                                    +1
                                    Ну значит, по Вашей логике, нужно в тех областях, где исторически используется линукс (серверы), избавляться от него (в мире миллионы администраторов windows, дешевле не сервер поставить винду, чем обучить нормального специалиста линуксу).
                                      +1
                                      Это, скорее, по вашей логике.
                                      Я имел в виду рабочие места, а не серверы. Серверы, как раз, лучше использовать линуховые, если это не специфичные сервера типа Exchange.
                                      Если что-то лучше работает в одном месте, это не значит, что оно будет лучше работать везде.
                                        0
                                        Ну так JavaScript и работает в одном месте — в браузере. Ну, с недавних пор, в некоторых специфических серверных приложениях.

                                        Следовательно, всё на своём месте и изобретать велосипеды не надо, ок?
                                          +1
                                          Да мне-то всё равно, вы это скажите Гуглу с его GWT и Dart, или Yahoo с его YUI, или Sencha с ExtJS — все они делают фреймворки, компилляторы или целые языки программирования, чтобы дать программистам привычную классовую модель вместо прототипной.
                              0
                              Когда-нибудь уже переведутся умники, думающие что люди, которые занимаются разработкой подобных вещей не разбираются в JavaScript'е?
                              Чтобы было удобнее работать с прототипной моделью в большом проекте придётся сделать аналоги YUI3 функций extend, augment, mix. А какой синтаксический сахар поверх них вы ещё наложите — это уже пофиг, кто-то использует Y.Base.create, кто-то использует напрямую extend, никакого принципиального отличия. Все отлично понимают как происходит наследование с использованием прототипной модели.
                                +1
                                > Когда-нибудь уже переведутся умники, думающие что люди, которые занимаются разработкой подобных вещей не разбираются в JavaScript'е?

                                Когда «люди, которые занимаются разработкой подобных вещей» перестанут выдавать вот такие трехколесные велосипеды, как в исходном посте.

                                > Чтобы было удобнее работать с прототипной моделью в большом проекте придётся сделать аналоги YUI3 функций extend, augment, mix.

                                Конечно, пара функций augment/extend для любого проекта пригодится. Только я открою Вам секрет: их вовсе не в YUI изобрели.

                                > Все отлично понимают как происходит наследование с использованием прототипной модели.

                                Я боюсь, в объективной реальности всё происходит с точностью до наоборот: _очень немногие_ отлично понимают, как происходит наследование с использованием прототипной модели.
                                  –1
                                  >Когда «люди, которые занимаются разработкой подобных вещей» перестанут выдавать вот такие трехколесные велосипеды, как в исходном посте.
                                  Я даже не понимаю какие могут быть претензии к автору, если он не призывает использовать его трехколесный велосипедом и сразу пишет «Ну, то есть всем конечно понятно, что в реальном, большом, проекте лучше всего для этого использовать функцию из какой-нибудь библиотеки( благо их много )»

                                  >Конечно, пара функций augment/extend для любого проекта пригодится. Только я открою Вам секрет: их вовсе не в YUI изобрели.
                                  Думаю что ваш секрет как минимум знают все кто прочитал javascript patterns.
                                    –1
                                    Простите, если бы Вы комментировали хотя бы мои ответы на свои вопросы, беседа была бы сильно содержательнее.

                                    >>> Когда-нибудь уже переведутся умники, думающие что люди, которые занимаются разработкой подобных вещей не разбираются в JavaScript'е?
                                    >> Когда «люди, которые занимаются разработкой подобных вещей» перестанут выдавать вот такие трехколесные велосипеды, как в исходном посте.
                                    > Я даже не понимаю какие могут быть претензии к автору

                                    Причем здесь «претензии автору»? Я отвечаю на Ваш вопрос: «умники» переведутся тогда, когда переведутся изобретатели велосипедов.

                                    >>> Чтобы было удобнее работать с прототипной моделью в большом проекте придётся сделать аналоги YUI3 функций extend, augment, mix.
                                    >> Конечно, пара функций augment/extend для любого проекта пригодится. Только я открою Вам секрет: их вовсе не в YUI изобрели.
                                    > Думаю что ваш секрет как минимум знают все кто прочитал javascript patterns.

                                    Мне что, табличку «сарказм» надо было приаттачить?
                                0
                                Можно посмотреть пример хорошего кода на чистых прототипах? Без костылей? Проблема в невозможности без костылей обойти вызов конструктора родителя. Значит роль конструктора должна быть какой-то другой. Какой же?
                                +1
                                Получилось даже не для чайников)
                                  0
                                  Если для чайников, 4-й пункт лучше все-таки убрать и сказать, что такого не бывает никогда в работающем коде. Нечайники это и так поймут. То есть 4-й пункт все-таки совсем не нужен.

                                  Остальное — ок. Разобравшись с внутренним устройством, можно посмотреть на Coffeescript: «class Child extends Parent» — почти дзен, еще бы вместо «class» что-то другое использовали, например, «type»
                                    0
                                    Все же почитайте Стефанова. То что вы описываете как множественное наследование — слияние. Я нередко ее использую, но чуть в другом ключе — слияние с одновременным связыванием (bind) методов, реализуют, то что называют mixins в «классовых» яп -иногда просто незаменимая техника.
                                      0
                                      упс — комент был к топику, а не к вашему комментарию
                                      0
                                      пункт появился просто вследствии топика, где автор предлагал доступ к родителям по порядковому номеру, что на мой взгляд вообще не в какие ворота не лезет. Так что, из за этого пункта если ктото всетаки решит что у него какраз подходящий случай, то пусть уж лучше будет доступ по имени, чем
                                      object.ancestor(name, level) - //указать имя метода/свойства и уровень углубления

                                      0
                                      Стоит обратить внимание читателей что this.super ВСЕГДА указывает на родителя самого последнего элемента наследования.
                                      Потому что this всегда, на всех уровнях погружения, указывает на этот последний элемент.
                                      Также рекомендую забыть про вызов функций «через-предка»(это относится к любым языкам программирования) и, по хорошему, забыть множественное наследование.
                                      Если требуется объединить два функционала в одном классе — есть миксины, которые вообще-то конечно подразумевают некий вид множественного наследования, но именно что некий вид.
                                      Когда мне пару лет назад пришлось реализовать такой механизм я долго думал как назвать функцию объединения веток — решил именовать interference. В JSDoc к функции идет рекомендация посмотреть значение слова на lingvo.yandex.ru. С значением автокомплита согласен, предупреждён и осторожен в использовании.
                                        0
                                        Возможно я вас неправильно понял, но вот смотрю сейчас под фаербагом
                                        Gentleman->prototype->super = Men.
                                        Programmer->prototype->super = gentelmen->prototype->super = Men.

                                        я вас неправильно понял, или что-то упускаю из вида?
                                          0
                                          нет, я именно про this.super.
                                          Сейчас то вы написали правильно, с указанием класса у которого искать этот super
                                            0
                                            ааа понял о чем вы :) если изнутри метода предка обратиться к this.super ожидая увидеть там своего предка то из за расширения контекста нас ждет большой облом. Тут помогает именование ( третий метод, который с __class_name ). сейчас уже нет времени но попробую придумать чтонибудь чтобы super из предка тоже работал как надо.
                                          0
                                          > ВСЕГДА

                                          Не всегда ;) gist.github.com/1330574 — можно вполне себе нормально использовать this.super, без хардкода имен классов.

                                          Существуют и другие методики, e.g.: github.com/DmitrySoshnikov/def.js/blob/master/def.js#L80

                                          Помимо этого, удобного this.super можно добиться через wrapper'ы.
                                            0
                                            Надо заставлять себя считать что удобный this.super это thisClassName.super.
                                              0
                                              А смысл? Если удобней просто this.super.
                                                0
                                                Вот он — классовый паразит.
                                                Не хочет он признавать и принимать сущность прототипного программирования.
                                                  0
                                                  Ну а все-таки (без «паразитов» ;)). В чем смысл?

                                                  P.S.: я достаточно глубоко знаю обе парадигмы.
                                                    0
                                                    Иногда требуется играть по правилам, используя те нативные методики языка, не пытаясь использовать его как некий другой язык.
                                                    В интернетах ну очень много различных методик о том как сделать js нормальный язык, десятки различных обёрток и фабрик классов.
                                                    Никак люди не наиграются :(
                                                      0
                                                      Понятно.
                                          +2
                                          Мы в проекте наследование не используем.

                                          Наследование само по себе в языке с dynamic dispatch не нужно, т.к. чтобы добиться полиморфного поведения, достаточно просто гарантировать, что ссответствующий метод есть у объекта. Причем, семантически не важно, лежит ли метод в самом объекте или где-то в цепи предков-прототипов, — в любом случае вызов пройдет — а это именно то, что нам нужно. Поэтому теоретически вполне можно обойтись простым копированием желаемых методов из одного объекта в другой (т.е. использовать миксины). Вопрос лишь в том, как разруливать ситуации, когда копируемый метод уже есть в целевом объекте. Тут два варианта — либо заменять, либо нет. Им соответствуют два метода из Underscore: extend и defaults.

                                          Управлять прототипами может быть удобнее в случае, когда иерархия классов достаточно глубокая: 3 и больше. Для простых же случаев описанной выше функциональности миксинов более чем достаточно.

                                          На своем опыте могу сказать, что вопросам наследования в JavaScript особенно много внимания уделяют начинающие разработчики. При этом основной вопрос, который их мучает, — это как с помощью прототипов реализовать классическое наследование. Особенно это справедливо для тех, кто приходит из статических языков, и потому с концепцией динамической передачи управления не знаком.

                                          С другой стороны, модель миксинов дается таким новичкам гораздо легче, и они начинают с успехом ее применять. Это не значит, что они так и не понимают работу прототипов, но в большинстве случаев знания о том, что метод может быть определен где-то в цепи прототипов, вполне достаточно для комфортной работы с языком.
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              Быстро не обещаю — ai-class.org и ml-class.org сейчас много времени отнимают. Но напишу.
                                              0
                                              При прототипировании чаще всего стараются уйти от лишних операций копирования методов/свойств при наследовании и порождении экземпляров.

                                              Миксины — да, они более гибкие. Потому что содержат как подмножество — классическое наследование.

                                              Что нужно, чтобы миксины выполняли классическое наследование?
                                              1) наследовать методы и свойства способом .extend() — когда методы потомка экранируют одноимённые методы предка;
                                              2) сохранять информацию о классах-родителях — позволит эмулировать instanceof. Достаточно прописать свойство ._parent на класс-родитель или для быстроты, содержать список родителей в массиве;
                                              3) прописывать свойство .constructor на самого себя, чтобы работал п.4 (я ничего не забыл?);
                                              3) остальное и без того поддерживается оператором new.

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

                                              Поэтому вопрос — получил ли проект выигрыш оттого, что он построен на противоречии парадигме ООП — очень даже неявный, и разработчики сами этого могут не знать. Может быть, они переизобрели некоторую парадигму, может быть, добавили свою.
                                                0
                                                Просто всё это более ресурсоёмко и мы лишаемся instanceof
                                                0
                                                спасибо огромное :) я даже кажется знаю про что будет следующая статья «для чайников». А еще — поддерживаю предыдущего коментатора — если несложно напишите про использование миксинов в реальном проекте.
                                                  0
                                                  Читая такие статьи, понимаешь что JS — язык особенный… Для понимания его концепций необходим особый склад ума. Которого лично у меня — нет :))
                                                    +1
                                                    У вас тут всюду фигурирует cosntructor вместо constructor, поправьте.
                                                      0
                                                      спасибо огромное :) поправил.
                                                      0
                                                      Начал изучать программирование. Читаю JS подробное руководство 6. До Constractor еще не дошел.

                                                      Перечитал этот кусок раза 4, но так и не понял суть:
                                                      image

                                                      Было понятнее, если бы было так:
                                                      класс 2 наследует класс1, который наследует класс 0, а также расширяет интерфейс А.

                                                      Пока этак книга конкретно меня парит (вечное: «Пока напишем так, а рассмотрим мы эти параметры страниц через 100-200»). Возникают мысли забросить изучение этого программирования
                                                        0
                                                        не пойму как тот же String или Array наследует методы Object. Ведь у них разные функции констукторы

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

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