Серия 2. Как выполнять методы предков в модификации прототипного наследования

    imageОформим начатое в habrahabr.ru/blogs/javascript/130495 в удобный для использования метод .inherit4 конструктора Constr, чтобы, фактически построить модель классов и наследования (она будет более мощной, чем классическая, но это побочный эффект). Если у Вас нет желания подключать Mootools с аналогичной моделью, будет достаточно этого метода на 2 КБ несжатого кода, чтобы нормально работать с прототипным наследованием и иметь пару дополнительных методов: доступ к методам предков .ancestor('имя_метода', номер_поколения_предка) и расширение хешей. Применение всех 3 методов позволяет исключить из лексикона слова prototype и constructor, продолжая работать с тем и другим, и делает код легко читаемым.

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

    1) улучшить написание и читаемость кода при написании дерева наследования — в частности, записать наследование через метод объекта, выступающего как класс (сейчас это — функция);
    2) использовать выполнение конструкторов, чтобы задействовать this.some_method =...; (сейчас они не выполняются, работают только прототипы, поэтому традиционное создание методов недоступно);
    3) добавить параметр для расширения прототипа конструктора — мы регулярно, после каждого наследования, расширяем прототип потомка новыми методами; надо поставить операцию на регулярную основу; в прежней статье приведена функция extend();
    4) встроить .ancestor в конструктор наследования (сейчас у нас это тоже функция).

    Фактически, мы хотим получить объект Class в понятиях Mootools, но имеющий доступ к предкам и выполняющий регулярные действия по подгрузке прототипа потомка. И вообще, код наследования улучшается, это — одна из целей построения метода.

    Про доступ к предкам в 1-й части прозвучала богатая по содержанию критика, спасибо всем, особенно, lalaki, AndrewSumin. Общее её направление — 1) доступ к предкам не нужен, разве что к непосредственному предку, согласно основным принципам ООП, иначе это говорит о некачественном проектировании модели и нужно менять модель. Также, 2) легко получить доступ через (имя_предка-конструктора).prototype.метод_предка.apply(this, параметры), но тоже замечания по ООП не следует игнорировать.

    Ответы:
    1. Совершенно верно, доступ к дальнему предку говорит о неправильности проектирования (принцип Лисков), за исключением случая, когда в прокрустово ложе наследования JS хотим вставить узел множественного наследования — где соединяются 2 и более классов (конструкторов). И о доступе к ближайшему родителю принцип говорит, что он допустим (как же иначе его использовать). Наша функция делает его в наиболее сокращённом виде, со 2-м параметром по умолчанию (1) — .ancestor('метод').

    2. Да, доступ через 4 слова "класс.prototype.метод.apply" есть, только добавляются 3 лишних слова вместо одного: имя класса и 2 служебных, а достаточно 'ancestor' и относительный номер узла. С другой стороны, при перестраивании структуры наследования многословное выражение не меняется, а номер узла может измениться.

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

    Оформление функции в метод для Function


    Можно записать метод inherit() (наследование) через прототип базового класса. Придётся перегружать базовый класс Function, и этот метод будет встречаться в каждой функции. Это плохо — засоряется каждая функция, возникает источник конфликтов с другим ПО, но создаётся очень компактный формат записи наследования (это говорит о нехватке в языке конструктора для наследования). Поэтому, для иллюстрации и лучшего понимания строения, приведём пример наследования с этим компактным кодом.

    Ссылка на пример (для просмотра работы и методов удобно пользоваться Firebug).

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

    Решение без перегрузки базового объекта Function


    Чтобы избежать появления методов в Function, создадим класс Constr (конструктор) — аналог конструктора Class в Mootools. Сразу возникает необходимость дополнительных движений — уже не можем писать (конструктор).inherit, потому что у функции его нет. Надо использовать или прототип конструктора (но это длинно), или объект Constr считать конструктором, но на самом деле конструктором будет функция в этом объекте, или каждый раз догружать функцию-конструктор определением inherit().

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

    Наследование в картинках


    Здесь очевидно, что многофункциональный код труден для понимания, поэтому приведена схема того, как работает один шаг наследования (применение метода .inherit4() ). В основном, он делает новый конструктор операцией new и обеспечивает, чтобы в новом конструкторе были: 1) прототип (prototype), 2) функция inherit4(), а в прототипе чтобы были как минимум 4 свойства: ancestor (функция), extend (функция), constructor (функция — ссылка на себя), _anc (функция — ссылка на конструктор-родитель) и 3) к ним — все свойства конструктора и его прототипа, причём свойства прототипа — более приоритетные и не требуют действий. Первые 2 свойства прототипа (ancestor, extend) тоже копируются автоматически и не требуют действий — в коде их копирования нет.



    Итог: запись функций работы с наследованием


    /**
     * Родительский класс конструкторов для наследования с расширенными возможностями. spmbt, 2011
     * @param {Constructor} Inherited - класс-наследник (или extProto, если пустая функция)
     * @param {Object} extProto - прототип наследника или пусто
     * @param contextArg0 - 1-й аргумент предка или пусто
     */
    var Constr = function(){};
    Constr.inherit4 = function(Inherited, extProto, contextArg0){
      var f2 ={extend: function(obj, extObj){ //расширение свойств первого аргумента
          if(obj ==null)
            obj = this;
          if(arguments.length >2)
            for(var a =1, aL = arguments.length; a < aL; a++)
              arguments.callee(obj, arguments[a])
          else{
            if(arguments.length ==1){
              extObj = obj;
              obj = this;
            }
            for(var i in extObj)
              obj[i] = extObj[i];
          }
          return obj;
        },
        ancestor: function(name, level, constr){ //доступ к методу предка
          level = level || (level ==0 ? 0 : 1);
          var t =this;
          return level <= 1
            ? (level
              ? constr && constr.prototype[name] || t.constructor.prototype[name]
              : t[name])
            : arguments.callee.call(this, name, level -1
              ,  constr && (constr.prototype._anc != Function && constr.prototype._anc || constr.prototype.constructor)
                || t._anc );
      }};
      if(!this.prototype || !this.prototype.ancestor){
        if(!this.prototype)
          this.prototype ={};
        for(var i in f2) //подключение пары методов к прототипу старшего конструктора
          this.prototype[i] = f2[i];
      }
      if(this === Constr && Inherited != Constr){ //сделать новый конструктор
        if(Inherited ===null)
          Inherited = Constr;
        return arguments.callee.call(Inherited, Inherited, extProto, contextArg0);
      }else{
        if(Inherited || (Inherited && typeof Inherited !='function' && !extProto)){
            //применять вызов без параметров, чтобы добавить extend + ancestor к конструктору без наследования
          if(!extProto){ //вызов с 1 параметром-объектом для прототипа и с возвращением новой функции-наследника
            extProto = typeof Inherited !='function' ? Inherited :{};
            Inherited = typeof Inherited !='function' ? function(){} : Inherited;
          }
          Inherited.prototype = new this(contextArg0);
          Inherited.inherit4 = arguments.callee;
          f2.extend(Inherited.prototype, {_anc: this, constructor: Inherited}, extProto);
          return Inherited;
        }else{
          if(this === window)
            return Constr;
          else{
            this.prototype.constructor = this;
            return this; //если без параметров, то возвращается объект-предок
          }
        }
      }
    };
    //Тестирование:
    A = Constr.inherit4(function(){this.prop ='A';}, {protoProp:'protoA'});
    B = A.inherit4(function(){this.prop ='B';}, {protoProp:'protoB'});
    C = B.inherit4(function(arg){this.prop ='C';this.propArg = arg ||'XX';}, {protoProp:'protoC'});
    D = C.inherit4(function(arg){this.propArgD = arg ||'XX';}, {protoProp:'protoD'}, '3thArgInCInh');
    	var c01 = new D('ArgInD');
    	
    //B.prototype._anc = B;
    Alert(c01['protoProp'], c01.ancestor('protoProp', 0), c01.ancestor('prop', 0), c01.prop) //'protoD protoD D D'
    Alert(c01.constructor.prototype.protoProp, c01.ancestor('protoProp'), c01.ancestor('prop', 1)) //'protoD protoD C'
    Alert(c01.ancestor('protoProp', 2), c01.ancestor('prop', 2) ); //'protoC B'
    Alert(c01.ancestor('protoProp', 3), c01.ancestor('prop', 3) ); //'protoB A'
    Alert(c01.ancestor('protoProp', 4), c01.ancestor('prop', 4) ,'-- prop берёт самый нижний метод, а не даёт ошибку'); //'protoA C'
    Alert(c01.ancestor('protoProp', 5), c01.ancestor('prop', 5) ,'-- protoProp берёт самый нижний метод'); //'protoA C'
    Alert(c01.ancestor('protoProp', 6), c01.ancestor('prop', 6) ); //'protoA C'
    Alert(c01.ancestor('protoProp2', 4), c01 instanceof A, c01 instanceof D, '-- отсутствующие методы - undedfined; instanceof - верны'); //'undefined D true true'
    Alert(c01.propArg, '-- свойство из аргумента конструктора C; ', c01.propArgD, '-- из аргумента конструктора D');

    (Работающий пример с рядом дополнительных тестов, описанных далее, смотреть с консолью (вывод — в console.log()).)

    Правила пользования (вместо документации)


    Создание корневого класса:
    имя_класса = Constr.inherit4(function(){конструктор_класса;}, хеш_прототип_класса, 1_й_аргумент_конструктора);
    //прототип может отсутствовать, аргумент может отсутствовать
    //конструктор может отсутствовать, а на его месте - {} или полный прототип_класса

    Наследование класса:
    имя_наследника = класс_предок.inherit4(function(){конструктор_наследника;}, хеш_прототип_класса_наследника, 1_й_аргумент_наследника);

    Создание экземпляра — как обычно, экземпляр = new класс();
    Индикатор наследования — как обычно, экземпляр||класс instanceof класс_предок;
    //при множественном наследовании instanceof не работает, если не выстроить дерево наследований в линию

    Обращение к методу предка:
    экземпляр.ancestor('метод', номер_поколения);
    //номер по умолчанию =1

    Возможное обращение к методу предка по имени класса (в коде не реализовано, ссылка #):
    экземпляр.ancestor(класс, 'метод', [аргументы_в_массиве]);

    Перегруженные операции:
    -----------------------

    Добавление одного хеша к самому себе:
    экземпляр_класса_Constr.extend(xeш);

    Добавление нескольких хешей к самому себе:
    экземпляр.extend(null, xeш, xeш, ...);

    Расширение любого хеша:
    имя_объединённого хеша = экземпляр_класса_Constr.extend(xeш, xeш, ...); //более одного


    Предполагаемая критика и ответы


    Разбор тестирования будет чуть дальше, а здесь — ответы для тех, кто уже сам разобрался.

    1) здесь выполняется конструктор класса вместо new F() в прежней функции; Среда будет нагружена чуть больше, если прикладной конструктор пустой и ничем не отличается от F(). Но появляется возможность конструировать свойства. Будет ли среда нагружена сильнее при появлении в конструкторе свойств? Нет, потому что иначе они объявлялись бы в прототипе, а подготовка их велась бы не в конструкторе, а где-то рядом, что грозит неструктурированностью кода. Поэтому лучше в конструкторе делать формирование тех свойств прототипа наследника, которые нам нужны и которые зависят от конструктора.

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

    3) Почему для предка предусмотрен только один аргумент contextArg0? Потому что попытка записать конструирование объекта в виде Inherited.prototype = new (this.apply(this, contextArgs)); — не работает. Но одного аргумента будет достаточно, чтобы задать хеш со всеми параметрами в нём — практически то же самое и более удобное описание аргументов.

    4) Зачем лишняя сущность if(this === window)...? Для перегруженного метода расширения хешей (с пустым 1-м аргументом или с 1 аргументом). При наследовании не используется.

    Использование аргументов конструктора


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

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

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

    К аргументу конструктора напрашивается функция автоматического разбрасывания хеша по свойствам this:

    function(arg){ //конструктор в наследовании
    	if( typeof arg =='object') //разбрасывание хеша по свойствам
    		for(var i in arg)
    			this[i] = arg[i];
    }

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

    Inherited.prototype = new this(contextArg0);

    расширенное:

    if(typeof contextArg0 =='object'){
    	Inherited.prototype = contextArg0;
    	f2.extend(Inherited.prototype, new this(contextArg0));
    }else
    	Inherited.prototype = new this(contextArg0);

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

    Если поменять местами порядок присваивания,

    	Inherited.prototype = new this(contextArg0);
    	f2.extend(Inherited.prototype, contextArg0);

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

    Разбор тестов и механизма действия


    Можно сравнить вид тестов с примером из первой статьи. Выполняется почти то же самое (плюс выполнение конструкторов), но код уже не такой рыхлый, аргументы встали на свои места. Это — следствие обрастания кода функции новыми проверками и движения к некоторой цели.

    В примере начали участвовать свойства конструкторов — те, которые возникают из "this.xxx". В тестовом примере мы аккуратно расположили буквы A, B, C, D каждую на своём уровне. В результатах видим смещение букв. Это вполне логично, потому что .ancestor извлекает прототипы (кроме c01.ancestor('prop', 0)), а прототип возникает у наследника: свойство предка пишется в прототип. Получается, что рядом видим: свойство — от предыдущего класса, прототип — от текущего. Только у экземпляра c01 в примере прототипа нет (не написали), поэтому он взят от предыдущего класса, поэтому первая строчка — «protoD D» — одинаковы по выводимым буквам, а в следующих наблюдается смещение.

    Чтобы свойства от прототипов и от конструкторов вели себя совершенно одинаково, понадобилось отлавливать условие if(this === Constr) ..., чтобы обнаружить первого предка и «зациклить» наследование. Работает, как и в первой части повествования, «бомба доброты», которая не даёт ошибки при обращении к отсутствующему свойству. Тем не менее, добились того, что поведение при доступе к «слишком раннему» предку одинаково для всех свойств: возвращается первое существующее свойство родителя или undefined.

    Тесты, работающие в примере по ссылке, показывают, как работает множественное наследование (неполноценно), как работают перегруженные методы в объектах, имеющих предка Constr. Об этом — чуть ниже.

    Модель наследования и её вариации


    Вот такая несколько причудливая модель наследования получилась, если задействованы и свойства конструкторов, и прототипы. Модель мощнее, чем классическое наследование, и если присмотреться, то в рамках неё (этого самого алгоритма на 30 строчек) можно реализовать аналог классического наследования более чем двумя способами — только через прототипы или только через свойства. Или наследование «с удвоенной частотой шагов» в таком порядке:
    1. Базовый класс — прототип конструктора А (свойства его хранятся там же, в прототипе);
    2. 1-й наследник — свойства конструктора А (хранятся в прототипе 2-го наследника);
    3. 2-й наследник — прототип конструктора В (свойства — здесь же, в прототипе 2-го наследника);
    4. 3-й наследник — свойства конструктора В (хранятся в прототипе 4-го наследника);
    5. ...
    Разумеется, это не чистое классическое наследование, потому что можно менять прототипы любого конструктора, следовательно, мгновенно менять свойства у всех наследников. Зато, свойства предков не хранятся в каждом классе (если только нет оптимизации в движке скрипта), и если избегать «ошибок» смены прототипов после создания наследников, получится классическое наследование, даже в режиме с «удвоенной частотой шагов».

    Множественное наследование


    По совместимостям, надо ещё сказать, что множественное наследование не поддерживается операцией instanceof. Заставить её реагировать на дерево предков можно только способом выстраивания дерева в линию предков. В то же время, за счёт специального финта в inherit4() с подмешиванием свойств прототипа, если он уже существует, нормально делается слияние ветвей от предков. Код с подмешиванием не включён в пример, поэтому наследование не будет работать — прототип получит значение только от последнего предка.
    Предок1 = Constr.inherit4(function(){this.prop ='prop1'; this.propA ='propA';}, {protoProp:'prot1'});
    Предок2 = Constr.inherit4(function(){this.prop ='prop2';});
    Наследник = Предок1.inherit4({protoProp:'prot11', protoPropA:'protA'});
    Наследник = Предок2.inherit4(Наследник, {protoProp:'prot2'});

    Правильный результат даёт выстраивание дерева слияний в цепочку "Предок1-Предок2-Наследник" наследований — случай, когда пригождается метод обращения к методам предков с поколением больше 1.

    Где можно применить удвоенную скорость наследования?


    Там, где структура наследования детерминирована и для каждого уровня мы знаем, что написать. Например, есть задача использования настроек программы по умолчанию (базовый класс), которые перекрываются «рекомендованными настройками», которые перекрываются «приоритетными рекомендованными», которые, наконец, перекрываются настройками пользователя. Всего — 4 уровня настроек, везде — отношения наследования. Очевидно, в JS их можно реализовать 2 конструкторами и наследованием описанного типа.

    В этом примере даже процедуры классов будут одинаковыми. В случаях посложнее — вполне можно писать собственные процедуры для каждого «полушага» наследования.

    Всё это не есть достоинство, а просто дополнительная возможность, которую можно не использовать, а писать по одному шагу наследования на операцию .inherit4(). И, конечно, нужно понимать модель наследования в JS для такого использования, одного знания классического наследования будет недостаточно.

    Расширение свойств — вытащим описание extend в «паблик», и ряд других


    Свойства, в которых нет необходимости, потому что решают побочные задачи, но код для них написан и используется. Это — метод extend для расширения хешей и связанные с ним возможности перегрузки аргументов — для использования в разных режимах, для разных целей. (Эти свойства уже реализованы в функции выше и в примере.) Зачем? Да хотя бы для более быстрого слияния хешей, чем в jQuery, где делается больше проверок типов.
    1) В самом деле, зачем добру (в несколько строчек функции extend) пропадать, раз уж оно написано и готово внедриться везде, где определили Constr? (Мможет не понравиться тем, что вставляет extend во все наследованные объекты, но нет проблем отключить.)

    2)… И, эх, гулять — так гулять, добавим в extend возможность расширения самого себя, если написан один аргумент или на месте первого аргумента стоит null.

    3) Дополнительное удобство — можем писать (конструктор).inherit4(), без аргументов, чтобы включить в прототип обе функции — extend и ancestor, если не собираемся наследовать этот класс (конструктор). (объект).ancestor('имя_метода') тоже будет иметь смысл, если порождённому объекту (объект = new конструктор();) впоследствии приписали прямой затирающий метод — метод прототипа конструктора (не самого конструктора) будет иметь «человекопонятный» доступ (наряду с объект.constructor.prototype[name] ).

    Пример (так будет понятнее, чем то же самое словами):
    Обратим внимание, что аналогичный пример для Function.prototype.inherit3 тоже так умеет, но записан по-другому.

    конструктор = Constr.inherit4(null, {a: 333}); //определили конструктор
    объект = new конструктор(); //создали объект без классов-наследников
    объект.a = 555; //затёрли свойство
    Alert(объект.a, объект.ancestor('a'), "--имеем доступ к предку для экземпляра"); //но имеем доступ к предку: 333 и умеем расширять хеши любым объектом:
    Alert(объект.extend({a:3}, {b:4}, {c:5}), "--расширение сторонних хешей с помощью любого объекта" ); //Object {a=3, b=4, c=5}
    

    4) Учитывая, что возвращаем первый аргумент, возможно создание безымянного наследника, чтобы потом ему присвоить имя (правильнее — его присвоить имени :) ).

    //Расширяем самого себя несколькими аргументами (первый аргумент - null)
    obj = new (Constr.inherit4());
    Alert( obj.extend(null, {a:1}, {b:2}) ); //Object { a=1, b=2, _anc=function(), ещё...
    

    5) В примерах мы уже забыли о том, что в первой части статьи приходилось писать явным образом прототип объекта — теперь это делается 2-м параметром, это наглядно и удобно:

    A = Constr.inherit4(function(){описание_корневого конструктора}, {хеш_прототипа});

    В таком формате:
    A = Function.inherit4(function(){this.prop ='A';}, {protoProp:'protoA'});
    код выглядит гораздо лучше, чем при разрозненном определении свойств.
    В итоге, видим, что код разросся «мясом», но каждый участок действует весьма эффективно.

    Не следует забывать, что базовому классу Object метод .extend не приписали, поэтому такое — {x:2}.extend({a:1}); — работать не будет (издержки воздержания от расширения базового класса). Но

    Alert( (new (Constr.inherit4(function(){this.x = 2;}) ) ).extend({a:1});

    — будет (даст объект {a:1, x:2, и пару функций} ). (Кошмар — так, конечно, никто не будет делать, но идея продемонстрирована (extend самого себя) и будет работать в случае более растянутых кодов.) Главное, что мы ничего не теряем (только засоряется пространство имён), функция extend уже была, её только присвоили в прототип корневого объекта Constr, как и ancestor.

    P.S. Если кто-то сообщит ссылку на описание хотя бы очень похожего подхода к наследованию или такого же, все мы будем очень благодарны — интересно узнать, куда приводит такой подход.

    P.S.2 Надо ли избавляться от бомбы доброты и как?
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +6
      при множественном наследовании я уж тем более не хочу обращаться к методам предков по порядковому номеру предка. вставил нового предка и всё непредсказуемо поехало нафиг.
        0
        Ваше замечание навело на мысль перегрузить и .ancestor. Просто проверяем первый аргумент на принадлежность классу Constr и делаем .ancestor(имя_родительского_класса, 'метод');
        ancestor: function(name, level, constr){
          if(name instanceof Constr)
            name[level].apply(this, constr);
          else
          //далее по тексту
        
        где у аргументов будет совершенно другой смысл — constr, например — массив аргументов. Зато получится самодостаточная система вызовов родителей через один метод. Может быть, это решит споры по поводу надобности? :) Я бы таким пользовался, пототму что apply в тексте программы регулярно рвёт шаблоны тем, что this не на месте, а так — нормально.
          0
          Точнее, не name[level].apply(this, constr);
          а name.prototype[level].apply(this, constr);
        –2
        вот поэтому Google и придумывает новый язык на замену javascript :). Все таки js, не смотря на свои очевидные преимущества, сложноват для понимания простого программера. Увы
          0
          А сторонники JS на этом месте скажут «вот потому не надо стремиться придерживаться парадигмы классов, а работать через прототипы». На что я бы ответил, что если отношения классов с наследованием в природе существуют, то их не избежать, а на чём выражать — на родных классах языка или на прототипах — дело третье.
          +3
          ААаа-аа-а-а-а-а!!!
            +4
            Фак мой мозг.
            Вот из-за таких извращений люди и считают JavaScript недоязыком.
              0
              Эмоции понятны, т.к. код сходу не прочитаешь, но поясните мысль: что вы находите извращением. Я предупреждал, что чистая реализация на паре функций из 1-й статьи гораздо проще для понимания, зато этот код лучше для применения. И я такое вижу во всех реализациях наследования.
                +1
                а) если код плохо читается — он плох, независимо от того, насколько он полезен;
                б) все проблемы наследования в js решаются парой функций augment/extend;
                  0
                  Вы хотите сказать, что код, оперирующий сложными понятиями — всегда плох, потому что плохо читается? Здесь написан код, оперирующий именно такими, поэтому его сходу не прочитаешь. Будучи некоторое время в теме, всё становится понятным. Это — примерно как читать код библиотек, не думая категориями библиотечных объектов, только и всего.

                  б) >парой функций augment/extend
                  Их очень по-разному иногда называют. Вы очень кратки, чтобы можно было понять, о чём речь :). У меня 3 функции, но ведь можно обойтись и одной!
                    0
                    > Вы хотите сказать, что код, оперирующий сложными понятиями — всегда плох, потому что плохо читается?

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

                    > Их очень по-разному иногда называют. Вы очень кратки, чтобы можно было понять, о чём речь :)

                    Прототипное наследование и карринг.
                      –2
                      … опять очень кратко. То и другое у меня есть. То и другое не способствует понятности.

                      Один афганец знал несколько английских слов, необходимых для общения. Но никак не мог понять, что такое «exclusive» — хорошо это или плохо?

                      Так и приведённые Вами термины — они приобретают разный смысл в зависимости от того, к чему относятся. Ну ладно, не буду отвлекать от работы.
                      +1
                      Соглашусь с forgotten по первому пункту (как минимум).
                      При проектировании и кодировании пытаюсь пользоваться одним принципом:

                      Простые вещи это результат сложного анализа, а сложные — результат простого нагромождения.

                      Любой плохочитаемый код — сложный.
                        0
                        Согласен, но всё равно не буду ограничивать себя в проверке функций, которые, возможно, понадобятся, на основании того, что они пишутся сложно. Пусть для начала сложно, но отработают своё. Потом можно выкинуть или переписать. Для демонстрации показываю разные примеры и предупреждаю, что идём от простого к сложному и то, что не нравится — можно выбросить. Пока же вижу общие слова «сложно», но ни одного предложения, что выбросить. ;)
                0
                Вы можете привести пример, когда нужно обращаться к методу по порядковому номеру предка?
                  0
                  Наиболее реальный пример, исходя из обсуждений, видится для случая, когда мы развернули слияние классов в линию: A -> C; B -> C; превратили в A -> B -> C (потому что иначе instanceof ни в какую не поддерживается, об этом раздел в статье; то, что в B примешается A — это вопрос другой, надо как-то разруливать, не пользоваться B в других узлах). Тогда для экземпляра C -> c02 обращение к одному предку (B) будет co2.ancestor('метод'), а к другому (А) — co2.ancestor('метод', 2), без нарушения принципа Лискоу. Остальные случаи, как обсуждалось, индицируют неправильную архитектуру классов. Ну и возможность обращения не по номеру, а по имени конструктора — комментарий habrahabr.ru/blogs/javascript/130713/#comment_4333908.
                  +2
                  Ваша увлеченность идеей сама по себе достойна уважения :)

                  Сам давно уже подхожу к архитектуре с принципом максимального упрощения, а именно:
                  1) каждый отдельно взятый объект и его связи должны быть максимально простыми — .ancestor это радикально нарушает;
                  2) не вводить архитектуру ради архитектуры (как признак — увлечение вставкой имен паттернов в имена объектов/типов, стремление к _максимальной_ гибкости) — здесь это стремление к множественному наследованию от типов с одинаковыми свойствами/методами;
                  3) если приходится изощряться — значит, что-то надо переделать; не можешь переделать — не изощряйся еще больше, маскируя сложность под простоту — станет только сложнее
                  4) для простоты — понимать и использовать конкретные принципы: SOLID + закон Деметры — как признаки для обнаружения слабых мест в коде

                  По-моему, Вы демонстрируете в целом позитивное явление — Javascript становится все более самодостаточной платформой разработки серьезных приложений. Поэтому и Javascript-разработчики проходят через «синдром архитектора» :)

                  Так что, хотя и в корне не согласен со статьей, признаюсь, что Ваша увлеченность нравится.
                    0
                    Спасибо за положительную оценку процесса :) и отрицательную — всего остального.

                    .ancestor появился, потому что надо было обращаться к перекрытому методу родителя и потому что намечалось множественное наследование. Возможно, что только первый уровень предка и понадобится реально, что можно было бы получить через .superclass (из исходной функции из начала 1-й статьи) или _anc, который я ей ввёл на замену.

                    .extend появился, потому что не нравится использовать $.extend() в таких базовых вещах. Все сложности с перегрузкой аргументов — исключительно ради него, чтобы понять в процессе работы, чем он (Constr.prototype.extend) может быть ещё полезен. Без выделения его как именованной функции код был бы раза в 1.5 меньше, но пока он ничему не мешает. Это как в тюнинге — есть возможность форсировать режим — почему бы не попробовать, а потом видно будет, нужно ли это. Для расширения прототипа — однозначно нужен.

                    Ну а далее — сейчас эту микробиблиотеку применяю к реальной задаче в интеграции с парой других библиотек. Ваши замечания обязательно учту, сам изо всех сил стремлюсь не заболеть архитектурным синдромом :).
                    0
                    Не стоит использовать arguments.callee, мне кажется.
                      0
                      Да, об этом в курсе. А почему?
                        0
                        зависимости нужно передавать через стандартные интерфейсы — например, через параметры метода — их должен обеспечивать вызывающий объект. arguments.callee() это нарушает: вызывающий объект не знает, что к нему могут обратиться и что-то от него ожидать. и всегда можно избежать использования callee()
                          0
                          ой, что-то я перепутал с Function.caller() — рассуждения про интерфейсы можно применить к нему, а вместо callee же можно использовать просто именованные функции.

                          кстати, интересна рекомендация даже анонимным функциям давать имена — в некоторых случаях облегчает отладку, т.к. вместо в стеке отображается понятное наименование.
                          0
                          На stackoverflow есть довольно подробный ответ.
                        +1
                        почитал собственно код:

                        1. Возможно, я просто не умею понимать такой код, но вроде это не прототипное наследование: прототип базового объекта никак не встраивается в цепочку прототипов наследника, и если изменить прототип базового класса, поведение наследника не изменится

                        2. Как бы я решал, использовать такой код или нет: поскольку это — «системный», а не прикладной функционал, в нем нужно быть уверенным гораздо сильнее — его баги будут гораздо сложнее. Поэтому: 1) код должен быть понятным, максимально стройным и устойчивым, в т.ч. покрытым тестами; 2) решаемая проблема должна оправдывать вносимую сложность. Код, на мой взгляд, сложен, но это отдельная тема. Самое главное — решаемая проблема для меня не оправдывает увеличение сложности в виде такого кода.
                          0
                          1. Когда я отлаживал этот код для множественного наследования, я превратил прототипное наследование в чисто классическое такими строчками. Вместо
                          Inherited.prototype = new this(contextArg0);
                          написал расширение прототипа через встроенную функцию:
                          if(Inherited.prototype){
                          	var tmp = Inherited.prototype;
                          	Inherited.prototype = new this(contextArg0);
                          	f2.extend(Inherited.prototype, tmp);
                          }else
                          	Inherited.prototype = new this(contextArg0);

                          Она и превращает прототипное в классическое тем, что копирует прототипы на каждом уровне. В результате, множественное всё равно неполноценно, а прототипное — исчезло (в остальном, внешне ничего не изменилось). Видимо, надо будет откатить обратно эти 6 строчек до одной.

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

                          Решаемую проблему я вижу не в одном доступе к предкам. Мне нужно наследование, поддерживающее выполнение конструкторов классов (и прототипное там, где возможно). Сейчас этот код его делает (код из первой статьи — нет).
                          +4
                          Увы и ах, кроме прикладного интереса, код не придставляет практической пользы.
                          1) Не достаточно понятен. И сложен в поддержке.
                          2) При первой возможности будет переписан в тот момент, когда Вы покинете фирмую
                            0
                            Мне хватает классов от Джона Ресига, или base2, или Ext.JS, или Backbone.JS.

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

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