Pull to refresh

Comments 110

Судя по таблицам его поддержки мы не будем им пользоваться в ближайший год. Судя по спецификации — классы ES6 — это синтаксический сахар над прототипами, и мне будет интересно сравнить их производительность со своим решением. Вообщем, пока ES6 наступит мне на пятку — я еще успею завоевать немного популярности :) И продукт развивается, так что время у меня еще есть.
Вроде же либы уже есть позволяющие использовать синтаксис ес6?
Когда начинал работу над ClassManager, то от Object.create сразу отказался — хотел поддерживать IE8. Спасибо что напомнили, померяю скорость, но результат мы получим тот же, так что для меня разницы нет.

Что насчет поддержки старых браузеров — то на начальной стадии она точно была, и сейчас скорее всего есть, но чтоб быть уверенным — стоит проверить заново.
Незабудьте про loose-mode. Ну и пользоваться es6 гораздо приятнее, чем любым костыльным синтаксисом.
«костыльным»? извините, но мой синтаксис не более «костыльный» чем синтаксис ES6. Когда вы видите мой класс — вы точно знаете, что будет сгенерировано.

Мой инструмент решает реальные задачи реальных людей. Если вам так важно видеть синие слова «public» «get» и «constructor» в IDE, то вам мой инструмент не нужен, пользуйтесь ES6.
В какой альтернативной вселенной ваш синтаксис удобнее es6 синтаксиса?
Lava.ClassManager.define('Lava.Animal', {
    Extends: 'Lava.mixin.Observable',
    name: null,
    toys: [],
    init: function(name) {
        this.name = name;
    },
    takeToy: function(toy) {
        this.toys.push(toy)
    }
});

class Animal extends Observable {
    constructor(name) {
        this.name = name;
        this.toys = toys;
    }

    takeToy(toy) {
        this.toys.push(toy);
    }
}

Ну и помимо удобства, конечно поддержка ide и стандартизированность.
С таким дерзким настроем мало кто будет пользоваться вашим поделием.
В FF на первом месте TypeScript, потом Native, потом ClassManager.

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

Как ваше решение работает с require.js?
Да прекрасно работает, только что даже проверил чтоб быть уверенным.
Там простой объект в стандартной обертке…
if (typeof module != 'undefined' && module.exports) {… } else { _global.Lava = Lava; }
Вот что мне однажды ответил mraleph по поводу Vanilla.prototype.toString.call(this), когда я делал что-то подобное ;]
Я думаю большая разница все-таки из-за того, что это prototype. Прочитать простое свойство это 1 mov, а прочитать prototype это куча кода
                  ;;; @60: load-function-prototype.
0x45729a09   201  8b73ff         mov esi,[ebx+0xff]
0x45729a0c   204  807e07b5       cmpb [esi+0x7],0xb5
0x45729a10   208  0f853006be11   jnz 0x5730a046              ;; deoptimization bailout 7
0x45729a16   214  f6460902       test_b [esi+0x9],0x2
0x45729a1a   218  751d           jnz 249  (0x45729a39)
0x45729a1c   220  8b730f         mov esi,[ebx+0xf]
0x45729a1f   223  81fea180002e   cmp esi,0x2e0080a1          ;; object: 0x2e0080a1 <the hole>
0x45729a25   229  0f841b06be11   jz 0x5730a046               ;; deoptimization bailout 7
0x45729a2b   235  8b56ff         mov edx,[esi+0xff]
0x45729a2e   238  807a0780       cmpb [edx+0x7],0x80
0x45729a32   242  7508           jnz 252  (0x45729a3c)
0x45729a34   244  8b760b         mov esi,[esi+0xb]
0x45729a37   247  eb03           jmp 252  (0x45729a3c)
0x45729a39   249  8b760f         mov esi,[esi+0xf]

В вашем бенчмарке была своя специфика (я правда уже не помню какая :)). В данном конкретном случае специфика немножко другая.

Если вам кажется, что тут я считерил — то напишите свои тесты, быстрее все равно не будет.


Легким движением руки

- NativeParentClass.prototype.method.apply(this);
+ NativeParentClass.prototype.method.call(this);


NativeInstance.method разгоняется на Хроме в 20 раз см jsperf.com/liquidlava-class-system-performance/9

А все почему? Потому что f.apply(this) Crankshaft не распознает. Он всегда умел f.apply(this, arguments) распознавать и недавно его научили f.call(...). Если вызов метода родителя распознается — то все проинлайнивается и различные инварианты типа LoadFunctionPrototype выносятся LICMом за пределы самого горячего цикла и, как следствие, на результат бенчмарка влияют мало (см IR цикла, красная полоса — индикатор вложенности циклов).

Кстати, LoadFunctionPrototype похудел, он все еще больше чем одинокий mov, но меньше, чем то чудовище, которое раньше генерировалось.

Хочется еще отметить, что замеряемый код содержит в себе один печальный антипаттерн — начальное значение this.counter экземплярам потомков приходит из прототипа, из-за этого если создать два экземпляра и дергать у них по-очереди method, то в этом самом методе возникает нездоровый полиморфизм, который может скушать на этом микробенчмарке до 50% производительности.

В целом, наследование в JS, особенно когда у базового «класса» много детей и часть методов ими всеми разделяется, в настоящее время это performance antipattern, потому что все разделяемые методы становятся мучительно полиморфными внутри. Поэтому в коде для которго действительно важна каждая инструкция наследования стоит избегать или насильно клонировать все методы с прототипа базового класса в прототип дочернего — причем не копировать по ссылке, а именно клонировать, например, с помощью компиляции клонов через Function-конструктор.
Собственно получается, что если уж делать убер-обёртку, то с полным клонированием методов и выносом свойств в конструктор. Вот тогда заживем ;]
Благодарю за консультацию, обновил тест и ссылку на главной.

Как я понимаю, тот факт что this.counter берется из прототипа на производительность не повлиял — по крайней мере, в этом тесте.
Он влияет только если создать больше одного объекта и у обоих дернуть метод пару раз — иначе незаметно (связанно это с тем, что inline caches проходят через PREMONOMOPRHIC state и это скрывает полиморфизм, второй объект выявляет это — потому что PREMONOMOPRHIC уже пройден и мы находимся в MONOMORPHIC). Посмотрите на Lava method call (2) в моей версии.
В том и дело, что ваш Lava method call (2) исполняется ровно с такой же скоростью как и остальные.
А в Firefox он вообще в 2 раза быстрее всех остальных.
Если взять открыть в чистой вкладке в Хроме и выполнить сначала «Lava method call», а потом «Lava method call (2)», то должна нарисоваться такая картина. Если их выполнить в каком-то другом порядке или допусти выполнить «Lava method call» еще раз после того как «Lava method call (2)» открутится, то «Lava method call» будет такой же медленный как и «Lava method call (2)», потому что type feedback стал полиморфный.

С FF картина интересная, все что я говорю — оно только к V8 относится. Скорее всего в этом микробенчмарке, он (совершенно правильно) понимает что полиморфизма тут быть не может. Ох, если бы только было легко посмотреть в их сгенерированный код, без пересборки jsshell из исходников.
Теперь вижу, еще раз благодарю за консультацию, буду улучшать производительность.
Только у меня падение скорости не в 2 раза, а так:
Lava method call in child class (client generated class) = 13,498
Lava method call in child class (server generated class) = 11,233
Lava method call (2) = 9,283
Конкретное значение уже зависит от CPU и т.д.

Вот после некоторых мучений нарисовал тест демонстрирующий проблему с полиморфизмом на FF

jsperf.com/liquidlava-class-system-performance-ff

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

Разрешите уточнить:
Правильно будет именно копировать функции, или же достаточно все начальные значения присваивать в конструкторе, а из прототипа убрать? (все-равно конструктор генерируется)
Это решает проблему с «отложенной» инициализацией полей и, как следствие, убирает проблему нестабильной формы объекта, копирование функций решает другую проблему — если у вас есть разные классы наследники, то функция сидящая на прототипе базового класса будет видеть их всех — и как следствие будет полиморфной, даже если каждый отдельный класс наследник будет производить объекты стабильной формы. Если каждый класс наследник пользуется своей копией «базовой» функции — то каждая такая копия мономорфна.
Посмотрел сообщения от RubaXa, вспомнил специфику. Там сравнивалось Base.prototype.method.call(this) и __.parent.call(this), где __ указывал на method в потомке, а __.parent на метод в базовом классе, т.е. Base.prototype.method. Причем все это было год назад, до того как V8 научился распознавать f.call. Из-за этого .call не проинлайнивался, и как следствие LICM не выносил LoadFunctionPrototype из цикла. Как следствие каждая лишняя инструкция была на счету и отражалась на результате микробенчмарка. (Плюс еще во втором случае на одно разыменование меньше, все складывается)
Все верно, собственно я тогда писал заметку о разных способах реализации super и придумался такой вариант, ну и ради красивой картинки сравнил их между собой (just for fun), что и вызвало мои вопросы.
В этом примере не будет работать оператор typeof, но на практике без него можно прекрасно обходиться. Я говорю про реальные приложения и задачи: если вам нужно отличать тип Animal от Cat — то это реальная задача, и она прекрасно решается. Но если вы хотите делать это оператором typeof — то извините, вам к другому доктору.


Эммм… Вы видимо опечатались. Возможно имелось ввиду instanceof, а typeof как бы возвращает у всех объектов «object».
1. Какие изменения в архитектуре приложения у вас произошли, и с какими трудностями столкнулись при переходе к ООП-коду? (особенности верстки и т.п.)
2. Можно ли при создании класса задать набор свойств? Есть ли их значение по умолчанию? Как вы присваиваете большое количество свойств?

Пример:
function Dog(name) {
    this.name = name || "Тузик"; // Когда таких свойств у объекта много, вы так же пишите или по другому?
}
1) В JavaScript я начинал именно с MooTools и ООП. У меня были большие проекты, где нужна надежность и качество, так что ООП — это красота и спасение, а не зло. Трудности возникают если есть лапша неструктурированного кода, которую приходится переписывать.

2) Конечно, можно. Разрешите пригласить вас в исходник основного фреймворка
— там больше 100 классов, которые решают задачи из реальной жизни.
Пример свойств по умолчанию:
Lava.define('Lava.Something', {
// все это будет скопировано для каждого экземпляра класса
name: "Вася",
names: ["Коля", "Петя"], 
number: 123
});
причем value-типы «name» и «number» будут вынесены в прототип, а names будет присвоен в сгенерированном конструкторе
То, что абсолютно нормально в С++, бывает очень вредным в JavaScript. Я говорю это из своего опыта: если в классах есть приватные методы и переменные, то такие классы часто становятся неподдерживаемыми. Если вы хотите что-то изменить в таком куске кода — то иногда вам не остается ничего кроме как выбросить старый код и переписать всё с нуля.

В C++ есть бесценный модификатор доступа protected. Без него код с классами и наследованием скатывается в одну из двух крайностей — либо все кишки торчат наружу, либо класс будет сложно расширить. Даже в ES6 такое не планируется, однако есть статья, где предлагается неплохой способ это реализовать. Могу перевести ее для хабра, если нужно.
Класс, спасибо за ссылку, благодаря вам открыл для себя Mozart.js, до этого пользовался jsface, но как-то был не в восторге.
Приватные члены — это плохая практика.

А как же инкапсуляция?
Инкапсуляция не нарушается. Приватный член класса — он так же является и защищенным, но с более сильными ограничениями.
Когда пишешь большое приложение на том же C++ — то почти все члены класса у вас будут именно защищенными, а не приватными, согласны?

Другие дело, что в JavaScript за настоящую приватность вы платите слишком большую цену, разговор был именно об этом, так что другими словами обмен получается невыгодным.
Когда пишешь большое приложение на том же C++ — то почти все члены класса у вас будут именно защищенными, а не приватными, согласны?

Нет, не согласен. Я, правда, пишу не на C++, а на C#, но у меня нет смещения баланса в сторону protected-членов (кроме абстрактных классов).
Вообще не согласен. Члены классы должны быть приватными, а всё что нужно наружу должно реализовываться через интерфейсы. Protected оправдан только для очень тесно взаимодейтсвующих классов, как правило в пределах одного юнита.
Приватные члены — это, строго говоря, т.н. «сокрытие данных», а не инкапсуляция.

Плохой практикой ее можно назвать по двум причинам:
1) Если переборщить с приватными членами, мы бессмысленно затрудним наследование пользователю.
2) Приватные члены в JS в лучшем случае эмулируются, и без препроцессоров эта эмуляция не очень удобна. Так зачем мучаться? ЕМНИП, в Python тоже их нет, но, вроде бы, никто такими вопросами не задается.
Приватные члены — это, строго говоря, т.н. «сокрытие данных», а не инкапсуляция.

Инкапсуляция, в том числе, включает в себя сокрытие данных.

Плохой практикой ее можно назвать по двум причинам:
1) Если переборщить с приватными членами, мы бессмысленно затрудним наследование пользователю.

Вы говорите только про JS, или про любой ОО-язык?
Нет, инкапсуляция — это объединение данных и поведение в одной сущности. Сокрытие данных, как видите, штука ортогональная.

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

О, wiki-war.

In programming languages, encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination thereof:

A language mechanism for restricting access to some of the object's components.
A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.

Some programming language researchers and academics use the first meaning alone or in combination with the second as a distinguishing feature of object-oriented programming, while other programming languages which provide lexical closures view encapsulation as a feature of the language orthogonal to object orientation.

Я отношусь к этим «some programming language researchers and academics», уж простите.

Вы говорите только про JS, или про любой ОО-язык?

что именно?

Вот это:
Если переборщить с приватными членами, мы бессмысленно затрудним наследование пользователю.
Ну так инкапсулировать можно и без приватных членов.
В ООП? Интересно, каким образом.
Зависит от терминологической базы. Я отношусь к той части сообщества, которая считает, что сокрытие информации является неотъемлимой частью инкапсуляции.
Согласен, но в чем цель сокрытия информации?
Что если скрыть сложность в псевдо-приватных методах/свойствах (они будут публичные, но о них не будет знать пользователь)?
Пользователь всегда знает о публичных методах и свойствах. Смысл сокрытия информации именно в том, чтобы пользователь не тратил силы на разбирательство, что он должен знать, а что — нет.
Для ЯП, в которых отсутствует механизм сокрытия данных, используется префикс "_" для сообщения пользователю о том, что на эти данные не нужно тратить силы для их разбирательства.
Префикс — это соглашение. Соглашение требует обработки. Обработка требует сил.

Именно поэтому для сокрытия данных лучше использовать механизмы языка, а не просто соглашения.
Все требует обработки. Как минимум в классе необходимо найти область public прежде чем изучать его интерфейс.
… однако при попытке обратиться к любому не-публичному свойству ошибка будет от компилятора, а не от собственного мозга. Вот и бонус.
Значит дело все таки не в сокрытии сложности, а в защите членов класса и переносе ответственности на компилятор (как в случае со строгой типизацией)?
Во-первых, это сокрытие сложности, которое усилено компилятором.
А во-вторых, в некоторых языках/платформах private-члены вам вообще никак не видны (ну кроме прямого влезания в код).
Во-первых, это сокрытие сложности, которое усилено компилятором.

Вот автор и считает, что это усиление компилятором излишне.

А во-вторых, в некоторых языках/платформах private-члены вам вообще никак не видны (ну кроме прямого влезания в код).

Без прямого влезания в код (чтение документации) можно скрыть и без private. Все зависит от используемого генератора документации.
Вот автор и считает, что это усиление компилятором излишне.

Ну и зря. Количество информации неизбежно увеличивается, ее сокрытие уменьшается. Другое дело, что в конкретно взятом js может быть невозможно реализовать истинное сокрытие информации — так это не означает, что оно не нужно, это означает, что его нельзя сделать, и надо смириться.

Без прямого влезания в код (чтение документации) можно скрыть и без private. Все зависит от используемого генератора документации.

Документацией дело не ограничивается. Еще есть API browsers и так далее.
в конкретно взятом js может быть невозможно реализовать истинное сокрытие информации

Возможно.

Еще есть API browsers

Если не лезть в исходники, то всегда все зависит от используемой системы.
Если не лезть в исходники, то всегда все зависит от используемой системы.

Еще зависит от того, что сразу заложено в платформу. Например, в .net изначально есть понятие публичного интерфейса сборки.
Я не знаком с .net, но думаю любой ее механизм можно перенести на ЯП, в котором не используется private.
Если вы это сделаете силами компилятора/среды выполнения, то это будет эквивалентно private. А если иначе — то это будет не тот же механизм.
Повторюсь, я не знаком с .net, потому могу ошибаться.
Ну, если язык позволяет создавать Интерфейсы, можно публичку описать в них и имплементировать в класс.
Ну как бы Интерфейсы есть не в любом языке. Опять же предложенный способ не каноничен, но если уж сложно найти методы с модификатором public, то можно и в отдельном Интерфейсе описать их.
Ну как бы Интерфейсы есть не в любом языке

Что мешает реализовать, в случае отсутствия?
Отсутствие множественного наследования, например?
Стало любопытно.
А есть языки, в которых нет интерфейсов, и нет множественного наследования?
Конечно.

(или вы имели в виду ОО-языки?)
Множественное наследование это другое. Для реализации интерфейсов достаточно хранить список реализуемых классом интерфейсов в виде системного свойства класса и проверить, реализованы ли все приведенные в интерфейсе методы в классе при его объявлении, иначе выдать ошибку.
Кому хранить, кому проверять?
Ну это уже вопрос реализации. Можно хранить прямо в классе, это позволит реализовать метод instanceof с учетом интерфейса, можно в неком менеджере, дабы не заполнять класс системными свойствами.
Вы говорите про проверки времени выполнения, реализованные прямо в коде силами программиста?
По другому никак. Не совсем программистом, можно использовать менеджер для этих целей.
… который написан программистом. Соответственно, каждый следующий программист, подключающийся к проекту, должен знать, что надо использовать менеджер, а не обычные объекты. И это уменьшение сложности?
каждый следующий программист, подключающийся к проекту, должен знать, что надо использовать менеджер

Как и каждый программист, должен знать синтаксис ЯП.

это уменьшение сложности?

Так дело ведь не в сложности. Если что то есть из коробки, это намного проще, чем сделанное руками, но речь идет о таких ЯП, в которых нет из коробки.
Как и каждый программист, должен знать синтаксис ЯП.

Меньше необходимых знаний — меньше ошибок, как это ни удивительно.

но речь идет о таких ЯП, в которых нет из коробки.

… а то, что вы получаете — не то же самое, что из коробки, а подобие.
Меньше необходимых знаний — меньше ошибок, как это ни удивительно.

Ничуть. Такой же объем знаний, такой же объем ошибок (если все написано качественно).

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

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

На деле разница будет только в двух аспектах:
  • На каком этапе обнаруживается ошибка
  • Защита от дурака, который зачем то решит полезть в закрытую область класса
Ничуть. Такой же объем знаний, такой же объем ошибок (если все написано качественно).

Не такой же. Нужно знать ЯП + ваш фреймворк. Вместо того, чтобы знать только ЯП.

На каком этапе обнаруживается ошибка

Этого аргумента достаточно.
Не такой же.

Но в ЯП с этой функциональностью ее все равно нужно знать. Так какая разница?

Этого аргумента достаточно.

Согласен, что это хорошее подспорье, но в JS нет промежуточной компиляции.
Но в ЯП с этой функциональностью ее все равно нужно знать. Так какая разница?

Это входит в знание ЯП.

Согласен, что это хорошее подспорье, но в JS нет промежуточной компиляции.

Alas.
Как бы много чего может помешать))) Максимум что у вас получится, если делать руками — это костыльная подделка, но зачем их вообще реализовывать руками, я не знаю. Мой кейс про интерфейсы был также эфемерен, как и ваше сетование на сложность изучения публичного интерфейса класса. На мой взгляд, ниче сложного в этом нет, особенно если присутствует группировка методов по модификаторам (public наверху) в коде. Интерфейсы все же для другого создавались)
костыльная подделка, но зачем их вообще реализовывать руками

Я тоже не знаю зачем их реализовывать, когда можно прекрасно жить без интерфейсов.

ваше сетование на сложность изучения публичного интерфейса класса

Вы меня с кем то путаете. Я подобного не говорил.

если присутствует группировка методов по модификаторам

У меня группировка по сверткам (folding) используется, это еще более простой вариант.
Private поля (и методы) решают вовсе не задачу сокрытия сложности. Не надо считать пользователей ваших классов идмотами, которых пугает код. Тем более, что информация как правило и не скрывается, можно посмотреть h файл.
Поля закрывают для гарантии того, что объекты класса будут находиться в инварианте на границах вызова своих методов.
И если класс предполагает уточнение своей реализации, т.е. не объявлен как final, то логично полагать, что потомки изменят и инварианты объектов. А, значит, private будет только мешать.
Конечно, если класс описывает сложный объект, часть состояния которого управляется только им и не должна быть доступна потомкам (подсчет ссылок, к примеру), private данные в нём уместны. Но вместе с тем в таких ситуациях может бфть правильным решением будет перенос части функционала в другой класс, который будет включён, или от которого будет наследование.

Так что получается, что private наиболее целесообразна для объявления методов, которые нигде не определяются :)
И если класс предполагает уточнение своей реализации, т.е. не объявлен как final, то логично полагать, что потомки изменят и инварианты объектов.

А как же LSP?
Не понял вопроса. Как вы связываете изменение инвариантов потомками и lsp?
Изменение инвариантов приводит к нарушению LSP (потому что объект унаследованного класса ведет себя не так, как объект базового).
Почему изменение инвариантов должно привести к изменению поведения? Инвариант это состояние объекта. При изменении инвариантов контракты менять вовсе не обязательно :)
Мы вот про этот инвариант говорим?

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

Имхо пред и постусловия относятся к тем ограничениям, которые обязательно должны быть покрыты тестами. И этой защиты достаточно. Можно и нужно исходить из того, что программист, наследующий от класса, должен понимать что и как реализовано в нём.
А объявление закрытыми полей, значение которых определяется алгоритмами, переопределяемыми наследниками, зачастую ведёт к ненужному увеличению сложности.
Как вы предлагаете устанавливать состояние объекта в производных классах?
Имхо пред и постусловия относятся к тем ограничениям, которые обязательно должны быть покрыты тестами. И этой защиты достаточно.

… и тест, который будет автоматически проверять на них все классы, унаследованные от x?

Можно и нужно исходить из того, что программист, наследующий от класса, должен понимать что и как реализовано в нём.

В этом случае мы снова теряем сокрытие сложности, и это плохо.

Как вы предлагаете устанавливать состояние объекта в производных классах?

У меня нет единого ответа. В каждом случае по-своему.
Зачем имена в строках и какие-то пространства имён?
Почему не просто:

var MyClass = Lava.ClassManager.define({
Extends: ParentClass
});
Есть весомая причина: в вашем примере ParentClass уже должен существовать на момент вызова define.
А с моим подходом я складываю тела классов в массив Lava.classes, и сами классы создаю уже в Lava.init().
Отложенное создание позволяет сделать monkey-патчинг для классов ядра (кто знает, что может понадобиться программистам, которые будут мой продукт использовать).

В вашем подходе этого делать нельзя, в лучшем случае заменяется целый файл в сборке, а в моем — можно заменить даже метод в классе, который находится в начале цепочки наследования, причем не изменяя файлы проекта.
Интересный проект. Система классов сильно напомнила классы из Sencha/ExtJS.
К чему то похожему пришел в VimScript, но в связи с особенностями (читать — ограничениями) языка, пришлось пойти по пути Perl.
Интересное решение, обязательно попробую его в каком нибудь проекте.
Это такой же вопрос как «Ява против C#».

ClassManager и TypeScript — это два разных инструмента, которые решают одну и ту же задачу разными способами.
TypeScript крут! Поддержка IDE, рефакторинг… но вот те, кто сидят на линуксе — не пользуются IDE от Microsoft. Лично мне без разницы, у меня стоит Visual Studio. Давайте поищем более существенные отличия.

В комментарии выше я уже упоминал, что классы из моего фреймворка можно monkey-патчить, а в случае TypeScript, если не ошибаюсь, то вам нужно заменить весь файл с классом. То есть в моем случае у вас больше контроля.
ClassManager позволяет патчить классы во время выполнения, а TypeScript нет.
Ну и, наконец, разница в скорости.

Какой инструмент выбрать — чаще всего зависит от решаемой задачи.
> но вот те, кто сидят на линуксе — не пользуются IDE от Microsoft
У них компилятор для node и source-map генерируются (хотя даже сгенерированный код легко и просто читать в большинстве случаев). Отлаживайте на здоровье :)
Очень странный тест, либо я ничего не понял. Зачем тестировать Backbone в такого рода тесте? В реализации наследования никакой разницы с нативным наследованием(через прототипы) не будет, а для ситуаций когда нужен «общий массив» то существуют другие структуры.
Для полной картины все таки желательно собрать побольше фреймворков. В статье было упомянуто, что если прототип присвоить как объект, то в Firefox падает скорость создания классов. Возможно, какие-то фреймворки используют Object.create — это обязательно будет влиять на скорость, или может знают некоторые хитрости, чтоб ускорить свои классы в определенных браузерах…
Всё что делает extend это
extend, опуская шаманства с конструктором и цепочкой прототипа
_.extend = function(obj) {
    each(slice.call(arguments, 1), function(source) {
      if (source) {
        for (var prop in source) {
          obj[prop] = source[prop];
        }
      }
    });
    return obj;
  };


т.е по факту просто копирует каждое из свойств(в том числе и prototype-chain) в новый объект, наследование там нужно только тогда когда некоторые функции родителя будут изменены со временем(например благодаря прототипам я не парюсь на счёт зависимостей и порядка выполнения и загрузки кода).

Я к тому что в этом тесте никакого смысла тестировать библиотеку нет, она ничего нового в классическую реализацию наследования через прототипы не вносит, да ещё и работает в разы дольше просто потому что взаимодействие между частями организовано по-другому(нет нужды в приватных переменных, а если ОЧЕНЬ надо — то они обычно организованы через замыкание), да ещё и всякая фигня при инициализации происходит(дефолты, сет, определение айдишника...). Так что слегка некоретно сравнивать один механизм наследования с цепочкой инициализации экземпляра класса.
В угоду «экономии на спичках» жертвовать нормальным синтаксисом, поддержкой IDE, все возможных линтеров, а в дальнейшем нативной реализацией, и получить уродский синтаксис, непонятную структуру но зато выигрыш в мифических попугаях.
А по опыту я могу сказать, что тормозить в вашем приложении будут не создание классов, а манипуляции с DOM и тупые алгоритмы работы с данными.
Он вроде пишет, что есть поддержка IDE
Похоже на то, что вы пытаетесь сделать JS более похожим на Python ( underscore-методы и переименования в цепочке наследования). Так что мне уже нравится. :) Ещё бы научить JS строгой типизации.
А вы сравнивали ваш генератор с тем же coffee.script?
С CoffeScript не сравнивал, но в нем используется наследование по методу Дугласа Крокфорда (функция extend). Могу предположить, что будет на уровне с остальными фреймворками, но если хотите быть уверенным — то лучше проверить самому.
Есть более красивый вариант, нежели
Cat.prototype.Animal$init = Animal.prototype.init
, и, кажется, не сильно дороже. Вот такой вот:
Cat.prototype.super = function(name, ...rest){
    return Animal.prototype[name].apply(this, rest);
};

(можно rest заменить на arguments, конечно)
Вызов this.super('init'); всё же смотрится лучше, нежели this.Animal$init();.
Sign up to leave a comment.

Articles