Pull to refresh

Comments 27

И опять, и снова. Боевым гопаком по граблям.

«Я совершенно уверен, что языку JavaScript не хватает трех вещей:
1) Нового фреймворка
2) Новой библиотеки, реализующей „классовую“ ООП
3) Библиотеки UI-виджетов»

P.S. Если серьезно, то у меня не хватает моральных сил даже начать тезисное описание откуда и докуда мне не нравится jWidget.
P.P.S. Angular.js, или там, Meteor.js, мне нравятся не намного больше. Но все же больше.
Вообще, конечно, здорово, мы все любим (кто знает) SOLID и паттерны, и большая работа проделана, и классический ООП-подход прост для понимания, но…

Чем вам так декларативность не угодила, интересно, ведь ее достоинства хорошо известны? Вот вы от нее отказываетесь, и получается куча тупого императивного кода:
var value = new JW.Property(1000);
var unit = new JW.Property("MW");
var functor = new JW.Functor([ value, unit ], function(value, unit) {
    return value + " " + unit;
}, this);
var target = functor.target;

Серьезно, 4 переменных?

И это вот
я много программирую на строго-типизированных языках программирования <...> Поэтому я большой фанат объектно-ориентированного программирования
Что ж, по-вашему, Haskell не строго типизированный?
Я не программирую на Haskell. К чему вопрос?

3 переменных всяко нужны: 2 исходных свойства и одно целевое. В AngularJS такие переменные кладутся в $scope. В EmberJS такие же переменные создаются неявно, и вы имеете к ним доступ через методы get и set. Четвертая переменная — functor — введена для читаемости кода. Как вы видите, в следующих примерах для функторов/мапперов переменные не создаются — они агрегируются, а используется только их target.

Я ничего не имею против декларативности. Если вам нравится программировать через шаблоны — пожалуйста. А кому-то другому больше нравится писать императивный код. В частности, императивный код хорош своим однообразием, подконтрольностью и расширяемостью.
Я не программирую на Haskell. К чему вопрос?
Не путайте парадигму (ООП) и типизацию, вот к чему.

А что такое «программировать через шаблоны»? Если вы про Angular-овские директивы в HTML — так это не единственный метод декларативно объявить биндинги. Да и незачем засорять код тривиальными деталями, в какой инпут какое поле модели пишется. Прекрасный пример декларативности в общем смысле — Backbone.View#delegateEvents.
Да, я неверно назвал объекно-ориентированность строгой типизацией. Спасибо.

В данном примере delegateEvents — это своего рода сахар, который избавляет вас от необходимости для каждого элемента писать this.$el.on("dblclick", this.open.bind(this)). Это нельзя назвать ключевой возможностью фреймворка. Я старался не перегружать свой фреймворк сахаром, т.к. это его усложнило бы. И для привязки обработчиков событий к элементам, и для изменения атрибутов, и для всех остальных операций с элементами предлагается использовать jQuery API, получая элементы через getElement или render[ChildId]. Напишите свою утилитарную функцию delegateEvents, если вам нравится объявлять события, как в Backbone — это несложно.

А вот шаблоны — это ключевая особенность фреймворков AngularJS и EmberJS. Весь функционал крутится вокруг них.

И то, что в Backbone при изменении модели весь View перерендерится с нуля — это тоже ключевой момент фреймворка. Прежде всего, именно по этой причине я в прошлом отказался использовать Backbone.
delegateEvents — это своего рода сахар
любое декларативное программирование — это своего рода сахар, за которым стоят простыни императивного кода, который все эти красивые объявления наделяет жизнью. Только вот в 2014 году мне уже лень писать свой сахар, если давно есть готовый — проверенный, покрытый тестами, с продуманным API.

в Backbone при изменении модели весь View перерендерится с нуля

Это уж как сделаете:) Прикрутить биндинг к бэкбоновским вьюхам, хоть через HTML-ные атрибуты, хоть через словарь в прототипе вьюхи — не rocket science, таких решений уже с десяток, наверное…
То, что я посчитал нужным, я сократил и вынес в методы или классы. delegateEvents я выносить нужным не посчитал. Конечно, в примере Backbone все красиво — аж целых 6 строчек удалось вынести из метода render, чтобы сделать их короче аж на 20 символов. Но в реальности обычно каждый компонент слушает 1-2 события. Взгляните на TodoMVC: в поле ввода слушаем keydown по Enter или сабмит формы (как удобнее), в TodoView слушаем dblclick, а все остальное вяжется со свойствами напрямую — в jWidget через Listener'ы, а в Angular и Ember через волшебные HTML-атрибуты.

Если вы начнете хитро биндиться с данными в Backbone, то вы пойдете в обход фреймворка. Это называется костылями. В документации четко написано, что когда модель выбрасывает событие change, представление перерендерится. Даже TodoMVC, где демонстрируются наилучшие (!) практики работы с фреймворком, это работает именно так. Оттого Select all и тормозит.
Но в реальности обычно каждый компонент слушает 1-2 события.
Да, часто так, но TodoMVC — это вырожденный случай, это демо.

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

В документации четко написано, что когда модель выбрасывает событие change, представление перерендерится.
А в следующем параграфе написано, что Backbone — это попытка понять, сколько сущностей минимально необходимо, чтобы строить современные приложения. И нигде не запрещено добавлять сущности, если надо.
Минимальность не обозначает тормознутость. Да и мне в 2014 году уже лень что-то переопределять в фреймворке, чтобы исправить его ошибки.
Попробуйте сами.

todomvc.local/architecture-examples/backbone/

Вот код.

function testSpeed(eventType) {
    var time = new Date().getTime();
    var i = 0;

    function tick() {
        var el = document.getElementById("new-todo");
        el.value = "a";
        var e = document.createEvent("Event");
        e.initEvent(eventType, true, true);
        e.keyCode = 13;
        el.dispatchEvent(e);
        if (++i < 500) {
            setTimeout(tick);
        } else {
            console.log((new Date().getTime() - time) + " milliseconds");
        }
    }

    tick();
}
testSpeed("keypress");


У меня в районе 9500 миллисекунд.

jWidget (http://enepomnyaschih.github.io/todomvc/labs/architecture-examples/jwidget/release/) то же самое, только «keydown». 10000 миллисекунд.

Я и не говорил, что в Backbone записи добавляются медленно. Я говорил, что Select all потом отрабатывает 3 секунды, а в jWidget — мгновенно.
Уточнение: запускается в консольке Google Chrome.
Честно говоря, не понятна методика измерения. То есть какой use case рассматривается, что пользователь будет собственноручно вбивать элементы по одному? Я сомневаюсь, что это разумно.
Куда интереснее сколько времени будет генерироваться представление, когда в него добавят сразу 500-1000 записей. Ведь это обычный кейс, списки приходят от сервера (из localStorage) и мы их добавляем в представление.
Так же если приводите результаты измерений, то лучше выводить их табличкой. Да и избегать оценок «мгновенно» и т.п. — нужны цифры. Тем более их можно получить тем же способом, что вы использовали для основного теста.
Спасибо за ваш комментарий. Он послужил поводом выполнить некоторые оптимизации. Мне удалось обойти Knockout, Angular и atom, но необходимо выполнить ряд тестов перед выкаткой в релиз. Это входило в мои ожидания, что хабра-сообщество укажет мне на ошибки фреймворка. В следующей версии фреймворка все будет исправлено.

Решения basis.js, jQuery, al-fast-list и оба JS нельзя ставить в один ряд с Angular, Knockout, atom и jWidget, поскольку там идут прямые манипуляции с DOM, что противоречит архитектуре MV*. Методы fill, update и clear должны работать только с моделью.
Ох, как лихо вы разделили ;)
Knockout не framework, angular не MV*. Хотя, конечно, зависит от того, что вы в это вкладываете. Если любая обвязка над значением и биндинги — это MV* фреймворк, то ок.

Решения basis.js, jQuery, al-fast-list и оба JS нельзя ставить в один ряд с Angular, Knockout, atom и jWidget, поскольку там идут прямые манипуляции с DOM, что противоречит архитектуре MV*.

Расскажите, где вы увидили прямые манипуляции с DOM в решение basis.js?
basis является каким то там MV* (не заморачиваюсь на эту тему) — есть и model, и своего рода view и controller.

Методы fill, update и clear должны работать только с моделью.

Модель не является обязательной сущностью. Эту задачу успешно может выполнять и контролер, и представление.
В любом случае «пузкомерка» не для того, чтобы разделить решения на категории и не было особых ограничений. Есть конкретная задача, нужно ее решить.
Конечному пользователю все равно на чем написано приложение и какие патерны используются, для него важно «работает» и «не тормозит».
Я не говорил, что Knockout и Angular — не MV* фреймворки. У меня к тому, что обведено рамкой, претензий нет.

Расскажите, где вы увидили прямые манипуляции с DOM в решение basis.js?


Объявляем basis.ui.Node и в методах fill, update, clear напрямую вызываем его методы setChildNodes, childNodes.forEach, updateBind, clear, которые выполняют DOM-манипуляции. Это точно представление, а не модель. Насколько мне известно, MV* архитектура предполагает, что вы меняете только модель, а представление само при этом понимает, когда и что нужно перерендерить.

Модель не является обязательной сущностью. Эту задачу успешно может выполнять и контролер, и представление.
В любом случае «пузкомерка» не для того, чтобы разделить решения на категории и не было особых ограничений. Есть конкретная задача, нужно ее решить.


Если бы решения Angular, Knockout и jWidget тоже работали напрямую с представлением, то, естественно, они работали бы гораздо быстрее. Я профилировал решение jWidget и обнаружил, что 50% времени съедают накладные расходы на работу с событиями модели.

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


А еще ему важно, чтобы приложение было легко расширяемым и код был понятным. Именно для этого придумали паттерн MVC. Если везде работать напрямую с DOM, то вообще никакие фреймворки не нужны, но код быстро станет слишком запутанным.
У меня к тому, что обведено рамкой, претензий нет.

Значит ли это, что к basis.js претензии есть? ;)

Объявляем basis.ui.Node и в методах fill, update, clear напрямую вызываем его методы setChildNodes, childNodes.forEach, updateBind, clear, которые выполняют DOM-манипуляции. Это точно представление, а не модель.

Это в большей степени контролер, который абстрагирован от DOM, и ему совсем не важно что там в шаблоне. Он поставляет значения экземпляру шаблона, а шаблон (который может считаться настоящим представлением) уже трансформирует значения в DOM операции.
Так то все приводит к DOM-манипуляциям. В angular делается вызов scope.$scan, в knockout это обновление observables. Не понятно почему в их случае все ок, а в решении basis.js что-то не так.

Насколько мне известно, MV* архитектура предполагает, что вы меняете только модель, а представление само при этом понимает, когда и что нужно перерендерить.

У MV* достаточно много трактовок, и каждый понимает по своему.
Да и представление в basis.js (шаблон) тоже само определяет, что, где и как нужно обновить. Что касается basis.ui.Node – то это абстракция, для организации интерфейса, и имеет косвенное отношение к нативному DOM. Чтобы не придумывать новое за основу была взята модель DOM.
На самом деле basis.ui.Node может являться и контролером, и моделью, и даже представлением при желании. Все зависит от того, что нужно сделать и чего добиться.

Если бы решения Angular, Knockout и jWidget тоже работали напрямую с представлением, то, естественно, они работали бы гораздо быстрее.

Это не так. В том же Angular нет моделей и нет оверхеда по их созданию. Зато есть оверхед на dirty check – и это основной тормоз. В knockout тоже нет моделей и очень дорогая организация структуры ovservables, которая является основной проблемой – а еще неэффективная работа с DOM (с которым как вам кажется он не работает). То есть это все в большей степени из-за архитектурные проблемы.

Я профилировал решение jWidget и обнаружил, что 50% времени съедают накладные расходы на работу с событиями модели.

Работа с моделями не должна съедать столько времени. Посмотрите мой доклад про данные – www.slideshare.net/basisjs/ss-32305540
В дополнение – вот вам решение на basis.js «через модели» и даже с коллекцией. Закономерно, что время увеличилось, но не в разы (на 20-25%): plnkr.co/edit/ZUWORGrGbtOsPJBgTik3?p=preview

А еще ему важно, чтобы приложение было легко расширяемым и код был понятным. Именно для этого придумали паттерн MVC. Если везде работать напрямую с DOM, то вообще никакие фреймворки не нужны, но код быстро станет слишком запутанным.

В вашем случае как раз есть проблема с понятностью, много кода и он запутан. MVC не серебренная пуля. А про DOM я уже написал, кажется вы не до конца разобрались ;)
К basis.js претензий нет :) Есть претензии к решению данной задачи на basis.js. Оно не соответствует архитектуре MV* в привычном смысле.

Один вызов $scan в конце обработчика события допустим, это не сложно. А вот явно создавать Nodes довольно тяжело. А если у вашей модели 10 представлений одновременно, все вручную обновлять будете?

Я ценю ваши достижения в области генерации DOM, но, пожалуйста, не подменяйте понятия. Во-первых, большинство фреймворков не разделяют понятия DOM и представления. Во-вторых, по архитектуре MV* обработка действий пользователя всегда осуществляется через модель, без обращения к представлению. В AngularJS это $scope, в ExtJS это Ext.data.Store, в Knockout это observables, в Backbone это Backbone.Model и Backbone.Collection. Ключевое преимущество, которое дает пользователю архитектура MV* — это возможность заводить несколько представлений для одной модели, возможность подменять представления и возможность менять данные налету, не задумываясь, какие у вас в данный момент существуют представления и в каком они состоянии. Пожалуйста, не путайте людей. Ваш фреймворк basis.js клевый, но это не MV* фреймворк в том виде, в котором он описан в вашей статье и продемонстрирован в «пузомерке». Еще я хотел бы посмотреть реализацию TodoMVC на basis.js.

MV*, конечно, не серебрянная пуля. Вы получаете лучшее качество кода, но теряете производительность. Принимать решение об использовании MV* подхода надо исходя из требований к динамичности приложения и максимальным нагрузкам.
Уважаемый, bakhirev.
Это, конечно, здорово, что вы взяли табличку из моей презентации. Но она не для того, чтобы гнуть пальцы или принижать чьи-то других, а для того, чтобы показать как быстро справляются с задачей те или иные решения. Методика измерений совершенно разная, и в данном случае нельзя сопоставлять цифры.
Судя по написанному в статье, работа проделана не малая. Уважайте других, пинать и критиковать проще, чем сделать что-то полезное.
Честно говоря, всё время, пока я читал вашу статью и смотрел примеры кода — я страдал.
Обилие кода для описания простых вещей мне почему-то до боли напомнило YUI3.
Лично моё мнение — написание большого приложения на jWidget принесло бы мне еще больше боли и страдания :)
Направление мыслей хорошее. Но получается слишком много кода и далеко не js-way. Особенно много по созданию классов и настройке рендеринга.
2. Скорость работы скрипта превыше всего.

4. Фреймворк работает на базе jQuery.

Тут определенно что-то лишнее. Если цель добиться высокой производительности, нужно забыть про jQuery, селекторы, html и render. Посмотри в сторону dom-based шаблонизаторов. Сейчас этой дорогой идут basis.js, react.js, ractive и даже meteor. В ember тоже идут в эту сторону, создавая HTMLBars. Вот пара моих докладов по этому поводу: раз и два.

Вообще советую посмотреть basis.js, в нем найдешь много знакомого. Например, те же именованные дочерние представления (сателлиты), или некоторые вещи из работы с данными и др. Только это все более развито…
Если не читал, вот первая часть руководства по фреймворку.
Но получается слишком много кода и далеко не js-way.

Согласен, на типичный JS не похоже. Но объектно-ориентированный подход легко можно будет перенести, скажем, в Dart. Это входит в мои планы.
Если цель добиться высокой производительности, нужно забыть про jQuery, селекторы, html и render.

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

Спасибо за ссылки, почитаю.
Sign up to leave a comment.

Articles