Pull to refresh

Comments 66

Не понятно зачем писать
this.constructor.super_.prototype.methodName.apply

когда можно писать
ParentClass.prototype.methodName.apply


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

И еще я думаю хорошей практикой есть писать
util.inherits(ChildClass, ParentClass);

прямо перед объявлением ChildClass, а не после
не понимаю почему все так не пишут, очевидно же, что при длинном конструкторе «провтыкать» наследование очень легко, а так все прекрасно видно кто от кого наследуется и в прототип раньше времени ничего не добавишь.
Посмотрел внимательнее — не имеет смысла эта поделка:
1. Не экономит печатные символы, поскольку метод родителя обычно нужно вызвать один раз. Так что писать
this.inherited + ParentClass.prototype.methodName.override
явно накладнее
чем просто ParentClass.prototype.methodName в методе
2. Не избавляет от необходимости бегать по коду в случае замены предка.

У меня альтернативный вариант, который позволяет писать короче и не засорять прототип Function:

function override(fn){
  var name = fn.name;
  return function(){
    this.inherited = this.constructor.super_.prototype[name];
    return fn.apply(this, arguments);
  }
}


И использование соответственно

ChildClass.prototype.methodName = override(function methodName(){
   this.inherited();
   // new code
})
Да, код становится существенно красивее, но смысла в поделке не добавляется, скорость исполнения у него такая же, как и Function.prototype.override, в 4 раза медленнее, чем через this.constructor.super_.prototype.methodName.apply(this, arguments).
А как насчет такого варианта:
function override(class,fn){
  fn.inherited = class.super_.prototype[fn.name];
  return fn;
}


Использование:
  ChildClass.prototype.methodName = override(ChildClass,function methodName(){
   methodName.inherited();
   // new code
})


Должно быть быстро и понятнее.
А еще красивше (на любителя) попатчить inherits:
exports.inherits = function(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  ctor.override = function(fn){
    fn.inherited = superCtor.prototype[fn.name];
    ctor.prototype[fn.name] = fn;
  }
};


Использование:
    ChildClass.override(function methodName(){
      methodName.inherited();
     // new code
   })

Переопределять util.inherits как-то не хотелось бы, есть все же надежды, что там не идиоты сидели, и есть какая-то низкоуровневая оптимизация, но я потестирую производительность, спасибо. А из предыдущего варианта хороший получился результат по производительности, еще в 3 раза быстрее, чем через this.constructor.super_.prototype.methodName.apply(this, arguments).
Вы вообще код util.inherits видели? Какие там могут быть оптимизации? :)

Кому-то просто захотелось свести 2 строчки к 1.
Этот код я уже постал в более читаемом виде тут, но надежды конечно же не на него, а на node.js и V8, в ядре node.js достаточно магии и исключений из правил, (много кода, который компилируется не так, как должен бы был) чтобы иметь такие надежды.
Т.е. всеми любимый Object.create по вашему в другом не деоптимизированном коде работает медленнее, чем в этой функции, или созданный в этом месте объект отличается от созданного в другом? :) Интересная точка зрения, интересно было бы посмотреть на тесты.
Это почти типичная ситуация для кода в ядре языка или платформы, такое было и в Delphi и в C#, называется compiler magic или просто магия. Но ее наличие нужно проверять, конечно, измерениями. Другое дело, что использовать util.inherits еще хорошо потому, что в новых версиях ноды он может быть оптимизирован, в том числе и с использованием новых возможностей ES6 и V8. Если в языке есть встроенный способ сделать наследование и он используется повсеместно в базовых библиотеках, то лучше уж его использовать.
Да кто бы сомневался, в v8 этого добра хватает, но конкретного случая это, если не ошибаюсь, не касается. Расскажите лучше о возможностях ES6, что могут хоть что-то поменять в плане оптимизации наследования без изменения кода самих конструкторов и методов, тоже очень интересно :) И Node.js — не язык и даже не движок, здесь это возможность фреймворка, притом топорно-велосипедная.
По поводу переопределения inherits, и в предыдущих вариантах тоже, мы с тобой упустили, что this в методах, и у родителя и у потомка ссылается не туда, а должен ссылаться на конечный экземпляр объекта. В общем, конечно большой соблазн написать все это без apply или call, но у меня не вышло. Из этого варианта с переопределением inherits поправил, чтобы this был правильный, положил тут. По скорости оно ровно такое же, как и вот этот вариант, который мне на текущий момент подходит больше всего.
Так не будет работать, но идея хорошая, наверное на ходу писали, но вообще направление понятно, наверное как-то так, меняем ChildClass на ParentClass в параметрах override и без всякого super_ через prototype сохраняем inherited:
function override(parent, fn) {
  fn.inherited = parent.prototype[fn.name];
  return fn;
}
ChildClass.prototype.methodName = override(ParentClass, function methodName(par) {
  methodName.inherited(par); // или methodName.inherited.apply(this, arguments);
  // new code
});

Ну по скорости это должно быть даже быстрее чем this.constructor.super_.prototype.methodName.apply(this, arguments) протестирую. Учитывая, что тут еще раз нужно писать от кого наследуем ParentClass, но скорость будет выше при варианте methodName.inherited(par) без apply, то на такие жертвы я бы пошел.
не понял, что там не будет работать?
function override(class,fn) { // class зарезервированное слово
  fn.inherited = class.super_.prototype[fn.name];
  // чем лезть через class.super_.prototype, можно сразу сюда передать родителя
  // и тогда имеем fn.inherited = parent.prototype[fn.name];
  return fn;
}

Использование:
// тут, соответственно, вместо ChildClass ставим ParentClass
ChildClass.prototype.methodName = override(ChildClass,function methodName(){
   methodName.inherited();
   // new code
})
Так смысл в том, чтоб родителя указать один раз. А работать должно.
Ну если уж наследника +1 раз нужно писать, то лучше уж я +1 раз укажу предка, и избавлюсь от этого surer_, а то глаз режет, это кто-то 1 января в 7 этот super_ придумывал, не иначе.
Я в AtomJS использую как раз такой способ. Больше всего меня в всяких «изящных» способах бесил отвратительнейший стек с этими функциями-врапперами

atom.declare( 'Foo', {
    testPrevious: function method (arg) {
        console.log( method.path, arg );
    }
});

atom.declare( 'Bar', Foo, {
    testPrevious: function method (arg) {
        method.previous.call(this, arg);
        console.log( method.path, arg );
        method.previous.call(this, 95612);
    }
});

new Bar().testPrevious(42);
/* Foo#testPrevious 42
 * Bar#testPrevious 42
 * Foo#testPrevious 95612
 */
Можно конечно написать и ParentClass.prototype.methodName.apply(this, arguments), не намного короче, но тогда класс предок указывается в двух местах, если не совпадет с предком в util.inherits(ChildClass, ParentClass), мне это как-то меньше нравится.

А вот по поводу порядка util.inherits(ChildClass, ParentClass) согласен, если кто-то вдруг объявит ChildClass.prototype.methodName до наследования, то ведь прототип во время наследования подставляется во какой:
exports.inherits = function(ctor, superCtor) {
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
};

В результате, если у предка был methodName, то у дочернего класса будет вызываться сразу метод предка и ни какого переопределения в дочернем не выйдет. Такую ошибку можно долго искать первый раз. А если человек не переопределение делает, а новый метод объявляет, которого в родителе не было, то конечно будет TypeError: has no method 'methodName'.
Можно в опрос добавить вариант «использую CoffeScript». Там и вызов super есть.

Вы еще не сталкивались с проблемой наследования методов класса? Я делал наследование для них на прототипах: github.com/printercu/coffee_classkit. С чистым JS тоже работает.
Добавил Вам вариант. А так разве в примерах привожу методы объекта? Тут же тоже все это именно с методами классов.
Я непонятно выразился. Метод класса — это из Ruby у меня определение. Во многих языках это называется статическими методами класса. А «обычные» — методы экземпляра класса.

Т.е. у вас childClassInstance.methodName('Tolstoy'); это instance-method. А ChildClass.someMethod() — метод класса был бы.
Использую ES6 можно добавить?
В ноде? Классы же еще не сделали в нем.
Так через транслятор же. Кофе тоже не нативное
И Вы видели тот говнокод транслированный, измеряли его скорость? Тут вопрос оптимального соотношения скорости и ужасности синтаксиса.
Тем, что для ноды есть общепринятый метод util.inherits, при помощи которого написаны все нативные библиотеки и ядро, я лишь сделал примеры его использования, потому, что статей с примерами на русском по решению типовых задач не нашел.
Тяжёлый случай. «Общепринятость» (да ладно?) велосипеда влияет на красоту кода и производительность, так и запишем.
ну так в ноде «общепринято» писать коллбеками, но это ж не значит, что промисы нельзя использовать?
Но когда скорость важнее, то колбеки их обгоняют очень просто.
Не думаю что в том, месте, где критична скорость люди будут связываться с прототипным наследованием (если уж свяжутся с JS вообще). Кроме того, есть, например, Bluebird — очень быстрые промисы. Есть еще Async, который не так сильно бьет по производительности, но тоже удобнее голых колбеков
Вы уж извините, что я лезу в ваши JS-разборки, но по своему опыту я давно понял: если нет аргументов против какого-то подхода — говори о его скорости. Ну на самом деле, в каком из ваших проектов наследование было узким местом? Я бы серьёзно задумался о его архитектуре (и о выбранной технологии), если бы в рантайме надо было постоянно породжать сотни тысяч объектов (ведь только с такого количества операций можно будет говорить об оверхеде).
А касаемо темы топика: если нативные инструменты либо медленные, либо уродливые и отказаться от них нельзя — то самое время задуматься о макросах/препроцессорах, которые и разработчикам нервы не вымотают лишними буквами, и тормозить не будут, тк развернутся в наиболее оптимальные конструкции.
Ситуация такая, я уже 2 года использую ноду, более того, использую ее как основной и единственный инструмент. И вот это первый раз, когда мне понадобилось наследование на классах, не наследование от встроенных классов, типа Stream или Buffer, а от своих, пока только с двойной и тройной глубиной иерархии. Честно говоря, в ноде классы вообще не нужны, даже в JavaScript мне гораздо удобнее без классов, но тут специфика проекта такова, что действительно нужны миллионы объектов в секунду. Код не прикладной, а системный, т.е. это не модель предметной области, там бы я удовлетворился фабликами, примесями, прототипным наследованием, да чем угодно, в модели предметной области редко есть критические по производительности задачи. Тут же высоконагруженный сервер приложений, развернутый на кластере из десятков собственных серверов, связанных в воедино и обрабатывающих десятки миллионов соединений. Вот такие дела, вот проект: Impress Application Server.
Ну так это ваш частный (и, согласитесь, крайне редкий) кейс, зачем его приводить как аргумент в споре об общих? В общем и целом (мне показалось, что и вы со мной согласны) аргумент «это медленно» применительно к инструменту наследования несколько не к месту.
В общем и целом, классы вообще не нужны в JavaScript, а уж тем более в Node.js. Есть мнение, что даже все ООП есть большое заблождение и хорошо справляется только с задачей, когда нужно занять много программистов на долгое время, например, посмотрите тут: blogerator.ru/page/oop_why-objects-have-failed И во многом я склонен согласиться с этим мнением. Есть только ограниченный круг задач, где ООП является продуктивной идеей. Как и в вопросе, что лучше, реляционки или документные/бессхемные базы, тоже всегда можно выделить сферы применения, где будет эффективнее или одно или другое. Попытка везде тулить ООП или реляционки или REST или MVC тот же привели к тому, что сейчас подавляющее большинство кода чрезмерно усложнено и сложность все нарастает. Еще шикарная статья по теме (посоветовали): davidwalsh.name/javascript-objects
Как то мы с вами вообще не туда ушли.
На мой взгляд любая методология, возведенная в ранг догмы, вредна и ошибочна. Но из каждой методологии можно почерпнуть здравые идеи, из которых и составить что-то своё.
Например, DRY или KISS очень прочно ассоциируются с ООП. Плохие ли это концепции? Однозначно нет. Плохи ли концепции перехода от общего к частному? Однозначно нет.
Вообще, ООП (или SQL) — это не какой-то урод, который портит всем жизнь, а попытка упростить предметную область для нетехнарей. Например, в терминах ООП очень удобно общаться с менеджерами потому что ключеые абстракции «объект» и «действие» очень понятны на интуитивном уровне. SQL, как мы с вами знаем, был придуман примерно для этого же. Кто знает, если бы не это умышленное снижение порога вхождения, где бы сейчас было всё IT? Мне кажется, что оно было бы уделом нердов в растянутых свитерах с тремя высшими, а не достаточно массовой и интересной штукой, которая во многом предопределила нашу современную жизнь.
Вот из-за того, что тонкие инструменты попадают в руки непрофессионалам, и происходит «весь этот горький катаклизм, который я тут наблюдаю...» В особенности применение инструментов в тех, местах, где им не место, ковыряние вилкой в глазу и намазывание соплей на мониторы…
Боже мой, прочитал я от бессонницы эту ссылку что вы мне дали, это какой-то кошмар, если честно. Вы правда считаете нормальным аппелировать к таким источникам и разделяете тезисы этих клинических идиотов?
Я в таком случае сливаюсь с дискуссии, вы меня извините, но с больным на всю голову фанатиками я не умею разговаривать.
Вообще обе статьи очень уважаемых в программировании людей с огромным опытом.
Вместо того, что бы ответить, почему правы вы, а не оппонент — лучше настрать в карму. Конструктивно ;) А ведь я, в отличии от вас, не писал, что ваш вариант — говно и медленный, а просто что он ничем не лучше остальных. Так держать!
Считаю, что все достаточно объяснено в статье и комментариях, после этого Вы первый переходите на личности.
Где в статье и комментариях объяснено, почему util.inherits быстрее Object.create? Где в статье объяснено, что трансляторы ES6 выдают медленный говнокод? :) Вроде это вам объяснено обратное, да с примерами.
Присланный Вами код не выполняет требуемого, нужно переопределять метод в дочернем классе с возможностью вызывать из него метод родительского класса, см. пример. И теперь берем сгенерированный говнокод, в котором и так видно, что решение получится существенно хуже, чем последний вариант. Обворачиваем его в тест скорости и видим, что он уступает в 3 раза этому варианту, не говоря уже, что добавляется еще зависимость от транслятора.
Потрясающе, ну хоть какие-то аргументы :) Ну раз так:

  • Вам был дан первый же ваш пример с наследованием из статьи, что он там выполняет и что не выполняет — уже ваши проблемы.
  • Про зависимость от транслятора — вы это сами придумали? Это JavaScript. ECMAScript 6. Без рантайма.
  • Опять это весёлое слово на Г… Объясните, где здесь говнокод и чем он существенно хуже вашего — инлайном метода наследования (которую можно таки не включать в код) или оберткой потомка для защиты от дурака? :) Идеально читаемый код, соответствующий airbnb style, не на много больше вашего.
  • Вот ваш пример на jsperf, с инлайном вашего util.inherits — в большинстве браузеров сейчас тот же v8. Скорость вызова метода — в 1.5 раза в вашу пользу — ну а чему удивляться — ссылка короче, получение из this и почти пустой метод. А вот скорость создания инстансов процентов на 30 у вашего ниже. И это сгенерированный код.
  • Я где-то писал, что это лучший вариант? :) Даже сгенерированный, по вашим же словам, говнокод, делает ваш, промолчим что, в части случаев.
Оу, а у вас ус отклеился — метод запускается без контекста — methodName.inherited(par);, а должен — methodName.inherited.call(this, par); — и скорость сравнялась.
  • Первый пример не содержит ни какой проблемы, это просто классика для Node.js, именно так написан весь код базовых классов и подавляющее большинство библиотек.
  • В названии статьи и введении четко написано — это для Node.js. Какие браузеры?
  • Опять же, к чему тут jsperf? Вы запускали это под Node.js?
  • Я что, заставляю кого-то писать так в браузерах?
Я ж для людей, что бы убедились, что не вру :) Кстати, запустил на ноде, убрал вашу ошибку и расскоментировал ваш methodName.inherited.apply(this, arguments); вместо call на jsperf — для вас всё совсем печально:
c:\>node inheritance6
Instantiation test
Processing time: 2515

Call method test
Processing time: 1527

c:\>node 6to5
Instantiation test
Processing time: 2459

Call method test
Processing time: 385
node inheritance6.speedtest.js

Instantiation test
Processing time: 744

Call method test
Processing time: 376


node inheritance7.speedtest.js

Instantiation test
Processing time: 869

Call method test
Processing time: 372


Или мы будем использовать для тестов одинаковый код или ничего не понятно, коммитьте в репу, не ясно, почему такое расхождение.
Оно самое. Похоже, тогда просто так повезло, но средняя разница по методам раза в 1,5 и не в вашу пользу :) Нода на этой машинке 0.10.33. Ладно, что это я… Сравнивать apply и call всё равно не корректно. А тест на jsperf выше с call в любом v8 браузере — полностью корректен и для ноды.
В ноде 0.10.33 версия v8 старая 3.14.5.9 (это еще 2012 год), а в хроме версии 39.0.2171.95 нынче v8 версии 3.29.88.17
Она и в 0.11 старая. И это одна из основных причин форка IO.js. Или вы предлагаете писать код только для устаревших версий движка? :)
Предлагаю писать для последней стабильной версии node.js. Кстати, еще соптимизировал inheritance8.js и он теперь дает 338мс. (в среднем).
Опять та же ошибка, только в другом месте. this не тот захватываете. Проверьте :)
Вот это я протупил, согласен, и еще, зачем-то присваивал результат override, без этого стало гораздо лаконичнее. К тому же, оказалось, что в node.js call работает быстрее, чем apply, это странно и непривычно. Результаты такие:

node inheritance6.speedtest.js

Instantiation test
Processing time: 656

Call method test
Processing time: 372

node inheritance8.speedtest.js

Instantiation test
Processing time: 524

Call method test
Processing time: 284
К тому же, оказалось, что в node.js call работает бфстрее, чем apply, это странно и непривычно

Так писал же выше. Это вы еще stack trace ошибок на Angular не видели, иногда приходится и такой ужас юзать.
this.constructor.super_.call(this, par1, par2);

А теперь представим, что у нас наследуется от Child третий «класс». Какого «родителя» вызовет второй конструктор в цепочке? :)
Заменить в каждом конструкторе this.constructor.super_.call(this, par1, par2); на ChildClass.super_.call(this, par1, par2); и все будет замечательно работать в длинных цепочках наследования.
Ну так замените. Заодно длину цепочки уменьшите и производительность поднимите. А еще лучше сразу на ParentClass — «говнокод» от компилятора догоните на инстансах, так как то же и получите :D
И если вы считаете цитату вашего нерелевантного ответа переходам на личности — мне вас жаль.
Ну так в кофе код тоже транслированный, но при этом абсолютно другой синтаксис. А так — будущее сегодня
ECMASCript 6 сложно было включить?

// Supertype
class Person {
   constructor(name) {
      this.name = name;
   }

   describe() {
      return "Person called " + this.name;
   }
}

// Subtype
class Employee extends Person {
   constructor(name, title) {
      super.constructor(name);
      this.title = title;
   }

   describe() {
      return super.describe() + " (" + this.title + ")";
   }
}
Акжан, и ты туда же? Ну что такое, я же и в заголовке и во введении пишу Node.js, там в harmony еще нет классов и общепринято использование util.inherits. Для прикладного кода да, можно юзать трансляторы из ES6, свои библиотеки, разные узкоспециализированные JavaScrhbgn-хаки, но для системного кода то негоже.
Использую наследование настолько редко, что нет нужды в отдельном костыле. Когда же надо субклассить что-то и не нужно вызывать суперкласс, то использую Object.create. Если же всё таки нужно использовать суперкласс, то вызываю напрямую ParentClass.prototype.overridenMethod, нодовский inherit не использую.

В опросе не хватает варианта «не использую наследование».
Sign up to leave a comment.

Articles