Будущее JavaScript MVC фреймворков

Original author: David Nolen
  • Translation

Представляем Om




Нам, в нашем ClojureScript-углу, это было известно уже давно — все наши структуры данных неизменяемы и основаны на оригинальных коллекциях из Clojure, написанных на Java. Современные JavaScript движки в настоящее время достаточно оптимизированы и мы часто наблюдаем производительность этих коллекций в пределах 0.4X от JVM.

Стоп, стоп, стоп. А какое же отношение может иметь производительность неизменяемых структур данных к JavaScript MVC? — Достаточно существенное.

Возможно, объяснить это будет не очень просто, но все же я постараюсь. Дело в том, что неизменяемые структуры данных, представленные в новой библиотеке Om позволяют создавать приложения на порядок производительнее, чем на популярных JS MVC фреймворков, таких как MVC Backbone.js (без ручной оптимизации). Om построен на прекрасном фреймворке от Facebook — React. Если вы не слышали о нём раньше, рекомендую посмотреть видео с JSConf EU 2013. Интересен тот факт, что из-за неизменяемых коллекций Om может продемонстрировать результаты лучше, чем при использовании React без каких-либо модификаций.

Бенчмарки, которые представлены далее, были созданы не для доказательства того, что Om – это быстрейшая библиотека в мире для построения UI. Они были созданы для демонстрации того, что очень часто принимаются решения, которые нельзя глобально оптимизировать. Также, современные фреймворки часто предоставляют небольшой объем документации и помощи, в связи с чем пользователи принимают решения, которые приводят к проблемам с производительностью.

Безусловно, проблемы в приложениях можно, утомительно, решать одну за другой. Но можно использовать фреймворки подобные Om, которые предоставляют комплексные уровни абстракции компонентов. Это поможет избавиться от целого ряда простых и трудоемких ручных методов оптимизации.

Игра бенчмарков


Откройте Om TodoMVC в браузере и запустите первый бенчмарк. Он создает 200 элементов списка, и на 11 дюймовом Macbook Air в браузере Safari 7 выполняется за 120ms.

Теперь откройте Backbone.js TodoMVC и запустите аналогичный бенчмарк. На моей машине страница отрендериловалось примерно за 500ms.

В Chrome и Firefox, на моей машине Om показывает результаты примерно в 2-4 раза быстрее чем Backbone.js.
Если вы попробуете переключить все элементы списка, Om сделает это моментально, тогда как при подобном действии Backbone.js продемонстрирует небольшое зависание.
Такая разница, вероятно, происходит из-за того, что Om перерисовывает страницу во время события requestAnimationFrame. Это позволяет существенно оптимизировать ваше приложение.

Давайте взглянем на профайлер в Chrome Dev Tools для этих бенчмарков. Мы можем увидеть большую разницу в работе React/Om из коробки по сравнению с не оптимизированным Backbone.js:

React/Om:


Backbone.js:


На мой взгляд, график React/Om показывает, что тут есть намного больше возможностей для глобальных оптимизаций и улучшений.

Хорошо, отличная работа! Но все же… Только сам факт увеличения производительности в 3 основных браузерах в 2-4 раза уже должен был заинтересовать многих. Особенно, учитывая то, что мы достигаем этого уровня благодаря неизменяемым структурам данных. Тут нет никакого увеличения в 30-40 раз, которое было сделано мной в Твиттере.

Теперь попробуйте второй Om бенчмарк – он создает 200 элементов, переключает их все 5 раз, а затем удаляет их. В Safari 7 на моём Макбуке это всё происходит примерно за 5ms.

Далее запустите Backbone.js. Убедитесь сначала, что удалили все элементы, а затем попробуйте второй бенчмарк. На моей машине в браузере Safari он выполнился за 4200ms.

Как такое возможно? — Просто.

Om практически никогда не выполняет ненужной работы. Данные, представления и логика контроллера не связаны между собой. При изменении данных, мы никогда сразу не ререндерим страницу. Мы просто откладываем перерисовку изменений до события requestAnimationFrame. По-сути, Om работает с браузером как с GPU.

Я предполагаю, что много JS MVC приложений повторяют архитектуру Backbone.js TodoMVC приложения, связывая вместе изменения модели и представления, а также других независимых частей, таких как сериализиация состояния приложения в LocalStorage. Всего лишь несколько фреймвоков предоставляют необходимую поддержку, для правильного построения архитектуры своих приложений. На самом деле, такое положение вещей не удивительно, поскольку большая часть фреймворков базируется на строковых шаблонах, CSS селекторах и прямом манипулировании с DOMом. Om оставляет позади все признаки place-oriented стиля программирования и другие потенциальные уязвимости в производительности.

Вы конечно же можете использовать Backbone.js или ваш любимый JS MVC фреймворк вместе с React, и это хорошее сочетание предоставляющее большую пользу. Однако, я не верю в событийно-ориентированные MVC-системы. И бенчмарки выше это подтверждают. Разделение моделей и представлений — это только первый важный шаг.

Надеюсь, это даст поводы для размышлений фанатам текущих JS MVC фреймворков, и даже людям, которые верят в использование только нативного JavaScript и jQuery. Выше я постарался показать, что компилируемый в JavaScript язык, который использует более медленные структуры данных, в конечном счёте быстрее чем его конкурент для построения более сложных интерфейсов. На верхушке списка лучших результатов находится Om TodoMVC с той-же функциональностью, как и все остальные фреймворки, объемом ~260 строк кода(включая все шаблонны) и уменьшенным весом 63K gzipped (это всё включает в себя: React — 27K, стандартную библиотеку ClojureScript, core.async, routing library, и несколько хелперов от Google Closure).

Если вы JavaScript разработчик, я считаю вам стоит хорошенько посмотреть на React. Я думаю в будущем связь React с библиотекой, которая обеспечивает использование стойких структур данных, например mori, могут привести JavaScript приложения к гибкой и очень хорошо настраиваемой архитектуре, которую предоставляет Om. Не смотря на то, что неизменяемые структуры данных генерируют больше мусора, мы все же надеемся, что современные разработчики справятся с этой проблемой. Также производительность устройств, которые мы носим в своих карманах, постоянно улучшается.

Технические описание следует далее.

Как это работает


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

В React существует достаточно важная функция shouldComponentUpdate, которая используется при вычислении разницы в виртуальном DOM, представленном вашими компонентами. Если она возвращает значение false, то React никогда не будет вычислять дочерних элементов этого компонента. Таким образом, React постепенно строит виртуальное DOM дерево, для получения данных о тех элементах, которые нужно изменить.

Как выяснилось, реализация shouldComponentUpdate по умолчанию слишком консервативна, потому как JavaScript разработчики как правило изменяют объекты и массивы. Поэтому для определения свойств компонента которые изменились, требуется вручную проходить по объектам и массивам.

Вместо использования JavaScript объектов, Om использует структуры данных из ClojureScript, которые, как мы знаем, не могут быть изменены. Исходя из этого, мы можем предоставить компонент, который реализует shouldComponentUpdate, с помощью использования наиболее быстрой проверки — на равенство ссылок. Это означает что мы можем всегда определить изменился в структуре за логарифмическое время.

Поэтому, нам не требуются операции на подобие setState, использующиеся для поддержки эффективных обновлений узлов дерева, а также хорошего объектно-ориентированного стиля. Обновление узлов дерева в Om, начиная с верхней точки всегда происходит быстрее, потому что мы только сравниваем ссылки по пути вниз.
Также, потому что мы всегда перерисовываем от верхней точки дерева, совмещённые обновления тривиальны для реализации. Мы не беспокоимся о поддержке совмещенных обновлений с React, так как он разработан чтобы обрабатывать за нас эти случаи.

И наконец, так как у нас всегда есть начальное состояние UI для каждой части данных, то ты можем просто сериализовать все важные состояния приложения. Больше не нужно беспокоится о протоколе сериализации, поскольку состояния Om UI всегда являются сериализуемыми.

Это также значит, что Om UI возвращает состояние мгновенно. Вы можете легко сохранить любое состояние в памяти и можете вернутся к нему, когда захотите. Это эффективное использование памяти, так как структуры данных из ClojureScript реализованы с помощью разделения памяти для них.

Завершающие мысли


В заключение, я не думаю что у текущего поколения JavaScript MVC фреймворков есть много перспектив в будущем. Я считаю, что если посидеть и поразмыслить над этим, то вы получите что-то нечто похожее на React/Om. Это сможет обеспечить оптимальный баланс между простотой, производительность и экспрессивностью. Тут нет ничего того, чего не было б известно раньше… Если вы станете воспринимать браузер как средство удаленной отрисовки страниц и перестанете хранить там всякий мусор, то все будет работать гораздо быстрее. Звучит как что-то знакомое, не так ли? Да, как программирование компьютерной графики.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 26

    0
    > В заключение, я не думаю что у текущего поколения JavaScript MVC фреймворков есть много перспектив в будущем.
    А LISP скоро покорит мир!
      0
      Om клевый. Но он еще сильно альфа, из документации только исходники, мало примеров, и пока не очень понятно как вообще правильно строить приложения на нем, да и API меняется довольно часто. Но все равно он чертовски многообещающий, и обязательно стоит на него посмотреть и поиграться.
        0
        В видео про React автор ругает Dirty Check за то что он может быть медленным, но при этом сам диффает Virtual DOM который уж точно не меньше модели по объему (а скорее больше), что как-то не вяжется, возможно я что-то не понял. И в связи с этим все тотже вопрос — как эффективно в парадигме React эффективно реализвовать «бесконечный» список из сложных объектов (диффать их представление точно дороже чем их модель, а даже этого делать не хочется).
          0
          Похоже я недостаточно внимательно читал: shouldComponentUpdate может спасти от дорогого dirty-check, но не от расходов на генерацию виртуального дерева. А подход с неизменяемыми объектами интересен.
            0
            Виртуальное дерево на то и виртуальное что не содержится в DOM, а значит не отрисовывается, нет никаких reflow/repaint и тд. Здесь известный джаваскриптер Стефанов немного работает с этой библиотекой www.phpied.com/category/react/
              –3
              >Виртуальное дерево на то и виртуальное что не содержится в DOM, а значит не отрисовывается, нет никаких reflow/repaint

              Спасибо, капитан.
                0
                пожалуйста, обращайтесь еще
          0
          По RequestAnimationFrame на хабре есть перевод.
            0
            Спасибо, заменил ссылку.
            0
            Еще нераскрытый вопрос: как это все сожительствует с уже существующей инфраструтурой: например если у меня внутри элемента гугловый график — как такое реализовать. Или даже просто мой собственный график на canvas — Virtual DOM меня не спасет.
              +6
              Сейчас JavaScript движки дошли до такого уровня, когда производительность коллекций стала в 2,5 раза выше, чем в JVМ.
              Доказательства можно?
                +3
                Это был вброс. Такое не доказывают.
                +1
                т.е. om быстр за счёт быстрого отслеживания изменений и обновления dom-элементов во время requestAnimationFrame?
                разве нельзя так же сделать в backbone.js? модели есть, при установке значения можно выставлять флаг isChanged ну или как-то так и… всё?

                Я чисто из любопытства спрашиваю: есть ли в om ещё какие-то киллер-фичи? (кроме той, что это ClojureScript ;-))
                  0
                  Первый абзац переведен неправильно, «this» относится к твиту.

                  We've known this for some time over here in the ClojureScript corner of the world — all of our collections are immutable and modeled directly on the original Clojure versions written in Java. Modern JavaScript engines have now been tuned to the point that it's no longer uncommon to see collection performance within 2.5X of the Java Virtual Machine.


                  Нам, в нашем ClojureScript-углу, это [т.е. факт, что производительность JS совсем не плоха] было известно уже давно — все наши структуры данным неизменяемы и основаны на оригинальных коллекциях из Clojure, написанных на Java. Современные JavaScript движки в настоящее время достаточно оптимизированы и мы часто наблюдаем производительность этих коллекций в пределах 2.5X от JVM [здесь, я думаю, 2.5X означает «2.5X медленнее», а не быстрее как вы перевели]
                    0
                    Исправил, спасибо.
                      +1
                      в пределах 2.5X от JVM

                      Это все еще в 2.5 раза быстрее, а должно быть 0.4x от JVM, если вы хотите оставить именно такую языковую конструкцию.
                        0
                        В оригинале используется именно такая конструкция «within 2.5X of the Java Virtual Machine», что переводится с сохранением смысла «в пределах 2.5X от JVM».

                        Как в оригинале, так и в переводе есть неоднозначность: непонятно, что понимается под мерой «performance» («производительность» — это ведь не «скорость») и как эта мера сравнивается «больше = лучше» (скорость) или «меньше = лучше» (время). Я написал Дэвиду DM в Twitter, попросил его уточнить, что понимается под этой фразой и «где бенчмарки». Посмотрим, что он ответит, когда проснётся.

                        Есть у меня вот такая ссылка www.50ply.com/cljs-bench/, на основе которой я могу сделать предположение, что «performance» — это «время».

                        мы часто наблюдаем замедление не превышающее 2.5X по сравнению с JVM
                      0
                      Вот теперь верится.
                      +1
                      Я никак не пойму, а почему сами браузеры не реализуют внутри себя такой же механизм обновления DOM'а?
                      • UFO just landed and posted this here
                          0
                          Можно какую-нибудь ссылку почитать об этом?
                          • UFO just landed and posted this here
                          +2
                          Статья маркетинговая, на самом деле никакого чуда в таком подходе нет. Ребята взяли не оптимизированное к подобному тесту Бэкбон приложение и наделали шумихи. Для интереса, я сравнил с фреймворком который используем Atma — разница лишь в ~50мс при создании, а всё остальное (удаление и изменения состояния) моментально. И то, этот 50мс `оверхэд` из за множества биндингов, а не из-за ДОМ рэндеринга. К тому же, если в ОМ добавлять `таски` до >2000 то лаги становятся довольно ощутимые.
                            +1
                            За примерами ходить далеко ненадо. Instagram.com полностью написан на этом фреймворке. И он довольно задумчивый даже в хроме на декстопе, не то что на мобильных девайсах
                          0
                          del
                            0
                            ractivejs.org/ реализует нечто подобное — параллельный DOM, перерисовки по событиям RAF и всякую экзотику типа Promise в датабиндингах.

                            Only users with full accounts can post comments. Log in, please.