Привет, хабраюзер. Поскольку, судя по всему, мы уже живем в будущем, то недавно я плотно засел за изучение новых фич ES6, ES7 и новых идей, предлагаемых React и Redux. И написал для своих коллег статью, в которой изложил сублимацию этих своих изысканий. Статья неожиданно получилась довольно объемной, и я решил опубликовать её. Заранее извиняюсь за некоторую непоследовательность изложения и отсылки к проприетарному коду из наших проектов — но думаю, что это всё же может помочь некоторым из нас лучше понять то, куда движется мир JavaScript, и почему не стоит игнорировать происходящее в нём.


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



Итак, поехали. Последние пару дней я беспрерывно изучаю наиболее современные JavaScript технологии, чтобы понимать, что вообще происходит в мире, куда двигаться, на что смотреть. Дело в том, что в фоновом режиме я уже довольно давно посматриваю по сторонам, но до сих пор не мог найти время чтобы углубленно изучить идеи, предлагаемые React или ES7. Тогда как мой опыт показал, что подобное игнорирование окружающего выходит боком — в частности, я когда-то очень много времени в своей работе потратил на возню с коллбеками (наверное, об этом стоит написать в отдельной статье), просто потому что мне впадлу было изучить Promise — хотя про эту идею я в общих чертах знал еще много лет назад. И только когда уже последняя собака стала эту концепцию использовать — я понял, сколько времени потратил зря, игнорируя эту замечательную идею. Причем оправдывая это тем, что якобы у меня нет времени, чтобы про что-то там читать! Это очень иронично. Недостаточно просто быть в курсе про что-то — иногда нужно реально засесть и изучить что-то основательно, чтобы понять это — и научиться использовать в своей работе.


Я бы не хотел чтобы подобное повторилось — тем более, когда речь идет о реализации достаточно серьёзного по сложности проекта, от которого зависит коммерческий успех бизнеса. Время это деньги, и иногда лучше потратить немного времени на research, чем потом однажды обнаружить себя, сидящего посреди мегатонн плохо работающего legacy кода, который проще переписать, чем рефакторить и отлаживать. Именно с такой мотивацией я начинал работать над многими абстракциями из нашего фреймворка несколько лет назад, и это было оправдано тогда. И я очень рад обнаружить сейчас, что все это время мир двигался в точно том же направлении: всё, над чем я работал в последние годы, я обнаруживаю в современных JS технологиях, ставших дико популярными за это время. Компоненты, трейты/миксины, контракты типов, юнит тесты, декларативность/функциональность, etc etc… Сейчас 4 из 5 новых веб-проектов пишутся на React — но еще пару лет назад про React никто не знал. Но всё это автоматически означает, что нужно осмотреться по сторонам и понять, как именно устроены все эти активно развивающиеся технологии, чтобы не наступать на грабли — и не повторять то, что где-то сделано, возможно, лучше.


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


Компоненты


В нашем [прим.: проприетарном] фреймворке все завязано на систему, имитирующую с помощью прототипов семантику классов — и добавляющую к ней дополнительные возможности:


ToggleButton = $component ({

    $depends: [DOMReference],

    $required: {
        label: 'string'
    },

    $defaults: {
        color: 'red'
    },

    onclick: $trigger (),

    toggled: $observable (false),
    checked: $alias ('toggled'), // синоним

    coolMethod: $static ($debounce ($bindable ($memoize (function () { ... })))),

})

Вот эти все штуки — $static, $trigger, $observable это теги, которыми «помечаются» поля в произвольных объектах. При конструировании прототипа эти теги считываются, трансформируя результат. Например $trigger сгенерирует поток событий, к которому можно забиндиться.


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


Поле $depends определяет список traits (миксинов), от которых зависит компонент. Трейты позволяют разбивать сложные компоненты на много маленьких кусочков, представляя компоненты как алгебраическую сумму их составляющих. В нашем фреймворке трейты очень продвинуты, позволяя склеивать структуры вроде $defaults на произвольную глубину, а также склеивать методы и обработчики событий в аспектно-ориентированной манере. Скажем, в одном трейте вы можете определить метод recalcLayout — а в другом подбиндиться к нему, просто определив метод afterRecalcLayout. Подобным образом можно биндиться также на потоки событий и observables. Traits могут зависеть от других traits. При сборке финального прототипа дерево зависимостей уплощается с помощью алгоритма топологической сортировки графа — подобно тому как разрешаются директивы require в системах сборки зависимостей. Это очень мощная и хорошо зарекомендовавшая себя абстракция, с её помощью построена архитектура многих ключевых вещей в нашем проекте.


Также все методы компонентов автоматически забиндены на this — таким образом их легко передавать куда-нибудь в качестве обработчиков событий:


button.touched (this.doLogout)

ES6


Изначально я делал вышеупомянутую систему исключительно потому, что в JavaScript не было классов — было неудобно определять акцессоры свойств, например. Ни о каких миксинах и декораторах речи изначально не шло. И если бы в JS были классы с самого начала, я бы вообще не стал это делать! И вот, в JavaScript наконец-то появились классы. Казалось бы — ага — наконец-то можно выкинуть этот самодельный костыль, который я делал все это время. Но не тут-то было. Классы в ES6 ущербны.


С помощью классов из ES6 вышеописанное сделать невозможно, поскольку определения классов в нём не используют объектную нотацию (JSON), и всё что мы можем определить — просто методы и методы-акцессоры, а возможности определять произвольные пары ключ-значения, как в объектах, мы лишены. То есть, мы не можем завернуть метод класса в $memoize, подобно тому как это можно сделать у нас. Мы даже не можем добавить поле $depends — в ES6 классах попросту нет такой возможности, вы не можете написать $depends: [...], это невалидный синтаксис.


Недавно я узнал, что не мне одному не нравятся классы в ES6. Оказывается, наиболее влиятельные и известные разработчики в мире JavaScript просто-таки ненавидят классы, призывая исключить class и new из стандарта вообще! Это не шутка, вот только лишь некоторые из статей с которыми я ознакомился недавно:



TL/DR: в классах нет никакого смысла, потому что всё что они делают, это предоставляют убогий синтаксический враппер над прототипами и Object.create. Убогий потому, что плодит сущности без необходимости: инстанциирование требует ключевого слова new вместо простого вызова функции, и раньше у нас были функции и объекты — добавились еще и «классы», несовместимые с функциями и объектами синтаксически.


Пример нашей разработки показывает, что можно достичь всего того, что дают ES6 классы — и намного больше — на чисто библиотечном (embedded DSL) уровне, не прибегая к модификации языка.


ES7


Продолжив изучение новых тенденций в JS, мне показалось, что классы всё же рано списывать со счетов. Некоторые языковые абстракции, предлагаемые для будущего стандарта, могли бы существенно облегчить пресловутый компилятор прототипов, представив его функциональность в виде набора нативных декораторов — поддерживаемых на уровне языка:


Button = @component class {

    @trigger onclick () {}
    @observable toggled = false

    @debounce @bindable @memoize static coolMethod () {}
}

Каждый такой декоратор это просто функция, которая трансформирует дескрипторы (определения), к которым применяется. Механика работы практически полностью идентична той, что применяется у нас.


Декораторы основательно захватили мое воображение. В попытках понять, как функциональность наших компонентов могла бы быть разложена с помощью нативных классов и декораторов, я замутил небольшой исследовательский проектик — прикрутив декораторы к ассертам из библиотеки Chai Spies. Не очень полезно, но помогло разобраться с декораторами и некоторыми другими ключевыми фичами ES6/ES7, связанными с метапрограммированием (Proxy Objects, Symbols).


К сожалению, со штуками вроде $required и $depends по-прежнему непонятно, как быть. Дело в том, что в JavaScript нельзя в классах делать подобные вещи, просто нет такого синтаксиса:


class Button {

    depends: [DOMReference]

    @required label: 'string'
    @required color: 'red'

Я погуглил, и наткнулся на черновик class public fields, где предлагается что-то такое:


class Button {

    depends = [DOMReference]

    @required label = 'string'
    @required color = 'red'

Но если декораторы это уже более-менее работающая (хотя бы на уровне proposal стандарта) вещь, то вышеупомянутый синтаксис это (судя по всему) пока еще довольно маргинальная идея, и неясно, подойдет ли это вообще для подобных целей — не факт, что эту мета-информацию можно будет прочитать и изменить с помощью декораторов… Пока что выглядит так, как будто бы нельзя.


Но прикол в том, что декораторы вполне применимы к полям в объектах. То есть, синтаксис вида @required label: 'string' в объекте выглядит вполне рабочим. Так может правы все эти чуваки из статей выше, и классы всё же нахрен не нужны? В общем, даже с учетом предлагаемых в обозримом будущем технологий, ту часть нашего проекта, что компилирует конструкторы типов из объектов — списывать со счетов пока не стоит...


Babel...


… это ответ на вопрос, почему же я сейчас так крепко задумался о будущих JavaScript-фичах, которые еще даже не в стадии стандарта. Это не имело бы никакого смысла, если бы не Babel — о котором я раньше тоже лишь слышал, но попробовать его в деле у меня руки не доходили. И очень зря!


Вкратце — это транспилятор JavaScript, превращающий ES6/ES7/ES.Next код в старый добрый JS, поддерживаемый в современных браузерах — которые и слыхом не слыхивали про эти ваши декораторы. Он обладает модульной архитектурой, позволяющей подключать новый синтаксис как плагины. К примеру, чтобы в моём коде заработали декораторы или упомянутые public class fields — мне достаточно было подключить соответствующие плагины к нему. Babel позволяет разработчикам использовать новейший экспериментальный синтаксис еще даже до его стандартизации — обкатывая его и генерируя фидбек. Именно таким образом мне удалось опробовать новые декораторы в деле.


Используя Babel, можно забыть о синтаксисе ES5 как о страшном сне — и не потерять аудиторию IE. Достаточно лишь настроить скрипт сборки, включив в него этот компилятор.


Ранее я избегал Babel еще и по той причине, что не хотел, чтобы код, запускаемый в браузере как-то отличался от кода, который я правлю в редакторе. Иначе непонятно, к какой строчке кода относится вылетевший эксепшен. Но с тех пор как я узнал про source maps — специальные файлы, которые указывают отладочным инструментам браузера, каким строчкам транслированного кода соответствуют строчки оригинала — это не видится мне большой проблемой.


React


Затем я пошел посмотреть, как делаются компоненты в React. Эта разработанная в недрах Facebook технология решает задачу построения пользовательских интерфейсов из наборов данных — и является наиболее популярной и многообещающей на сегодняшний день, из подобных. Изучая её, я обнаружил, что их компоненты сделаны похожим образом: там тоже есть сборщик прототипов, миксины, контракты типов...


Button = React.createClass ({

  mixins: [SomeMixin]

  propTypes: {
    label: React.PropTypes.string.isRequired
  },

  render: () { ... }
})

По сравнению с нашим проектом, у React очень примитивные классы — не позволяющие навешивать какую-то мета-информацию на поля и расширять поведение компилятора прототипов с её помощью. То есть, в React просто захардкодены наиболее распространенные паттерны — и этого пользователям Реакта хватает. Причем это абсолютно резонно, поскольку грядущие нативные декораторы решают 90% проблем, решаемых нашими наколенными «декораторами» — и эти нативные декораторы можно использовать уже сейчас, с помощью Babel. Поэтому Реакту нет никакой необходимости заморачиваться с этим.


Но на самом деле, React вообще не про классы! Чем больше я читал про Реакт, тем очевиднее мне становилась мысль, что эти чуваки пытаются достичь того же, чего и я — избавиться от классов, перейдя на чистые функции. Именно осознание этого заставило меня основательно забуриться в React и его механизмы, чтобы выяснить, как же, черт возьми, они хотят это сделать. Потому что это и есть сверхзадача, которую я поставил перед собой когда-то давно — назад начав реализовывать в нашем проекте механизмы для функционального реактивного программирования. Но если у меня это все находится всё еще в стадии зачатков — я куда больше внимания уделял элементам метапрограммирования, чем этой задаче — то парни из React, похоже, достигли серьёзных успехов на этом поприще. Причем, несмотря на название, «реактивность» они реализовали совершенно не таким образом, как обычно подразумевается в мире FRP… Я все еще пытаюсь понять, насколько это хуже или лучше — но как бы там ни было, это и сделало их проект столь ошеломительно популярным в мире. На самом деле, никому не интересны классы, как самоцель — интересно сделать так, чтобы дорога от данных к интерфейсу была наикратчайшей. В идеале — просто функцией.


Интерфейс как функция от данных


Если вдуматься, то большая часть наших так называемых «компонентов» делает что-то такое:


ToggleButton = ({ label, toggled }) => <button toggled="{toggled}">{label}</button>

ToggleButton ({ label: 'Рили?', toggled: true }) // <button toggled="true">Рили?</button>

То есть, идеализированный интерфейс — это просто чистая функция от состояния, выплевывающая DOM. Проекция. Меняются данные — меняется интерфейс. Зачем же вообще понадобились какие-то классы, WTF? Почему бы не обойтись просто функциями изначально, ведь функции были в JavaScript c самого его начала?


Давайте посмотрим на «классный» вариант ToggleButton (псевдокод):


ToggleButton = $component ({

    $required: { label: 'string' },
    $defaults: { toggled: false },

    render () { return <button checked="{this.toggled}">{this.label}</button> }
})

new ToggleButton ({ label: 'Это лучше, что-ли?', toggled: false }).render ()

Это то, как большинство компонентов пишется сейчас. Вы не видите здесь нечто подозрительное, сравнивая это с предыдущим вариантом? Да это же обычная чертова функция! Просто мы вызываем её очень странным образом (с помощью new), странным образом обращаемся с её параметрами — и странным образом получаем результат вычисления. Раньше мне никогда не приходило в голову так смотреть на компоненты — но упомянутые ранее ненавистники ES6 классов помогли мне наконец-то увидеть эту симметрию.


Вы скажете — но ведь в вышеприведённом компоненте у нас есть возможность задавать дефолтные значения параметров и контракты типов. Стоп. А разве в функциях так делать нельзя? Во-первых, дефолтные значения в ES6 можно указывать как для параметров функций, так и в destructuring-выражениях:


ToggleButton ({ label, toggled = false }) => ...

Во-вторых, буквально сегодня мне попалось на глаза вот это: Flow vs TypeScript. Посмотрите. Это пятиминутная презентация, рассказывающая о современных достижениях в области статического анализа типов в JavaScript. Это невероятно. Flow позволяет задавать и статически (на этапе компиляции) проверять контракты типов для свойств в JavaScript — так что наш ToggleButton мог бы выглядеть так:


ToggleButton = ({ label : String, toggled = false }) => <button toggled="{toggled}">{label}</button>

И при этом обязательность и тип свойства label проверялись бы еще до запуска программы! Что не снилось этим «классам» — с их вручную реализованными проверками, происходящими в run-time. Таким образом, никаких run-time исключений — вы просто не сможете скомпилировать подобный код:


ToggleButton ({ toggled: false }) // забыли указать label

В свете этого, «классовая» запись выглядит просто как кривой бессмысленный костыль — и оно бы таким и было, если бы не один важный момент, который и заставляет нас иметь в языке классы — вместо простых чистых функций, преобразующих наши данные в красивые кнопки с помощью map и reduce...


Дело в том, что такой чисто функциональный подход предполагает, что отрисовка интерфейса работает как что-то типа вызова console.log — вы не можете поменять нарисованное на экране никаким другим образом, кроме как вызвав функцию отрисовки заново, с новыми данными. Но мы не можем просто перерисовывать весь долбаный интерфейс при малейших изменениях в исходных данных — это как покупать новый автомобиль каждый раз, когда приходит время сменить масло в коробке! Разумеется, это сработает — но это безумно дорого, и в реальной жизни никто себе не может это позволить. Вы не можете пересоздавать заново весь чертов интерфейс фейсбука, когда в маленьком текстовом поле ввода чата добавляется один новый символ. Вам нужно поменять только тот участок в DOM, который связан с этим символом.


И тут мы подходим к тому, для чего вообще понадобились классы — для инкапсуляции скрытого состояния, кэширующего результат вычисления. Типичный компонент в «классическом» подходе хранит в себе ссылки на DOM-узлы и занимается менеджментом их жизненного цикла — создает, уничтожает, обновляет их состояние при изменениях в данных. Фактически, это и есть то, чем занимались UI-программисты большую часть своего рабочего времени — вновь и вновь решали задачу синхронизации отрендеренного DOM с актуальными данными в минимальное количество шагов. То есть, если где-то глубоко в JSON-структурах, описывающих наши данные, поменялось то маленькое одинокое поле, что связано с атрибутом toggled у конкретной нарисованной кнопки, нам нет никакой нужды заменять весь чертов DOM всего нарисованного интерфейса — достаточно вызвать setAttribute ('toggled', toggled) у этой конкретной кнопки. Но вот понять, как именно изменение в ваших данных должно быть связано с этим конкретным вызовом — это и есть самая сложная задача, которую успешно решает React, сводя её практически до абсурда в своей кажущейся простоте: вы просто «перерисовываете» весь интерфейс, как будто бы это и правда работает как что-то вроде console.log:


ToggleButton = ({ label, toggled }) => <button toggled="{toggled}">{label}</button>

То есть, компоненты в React реально могут выглядеть как чистые функции. Но при этом работают так же производительно, как и вручную (ad-hoc) сделанные компоненты с ручным менеджментом обновлений… и даже намного производительнее, что самое удивительное! Потому что React решает эту задачу в общем, для всей программы. Тогда как ручная реализация несовершенна и постоянно «срезает углы»: там где вы предпочтете не морочиться и сделать тупой «тотальный ре-рендер» того или иного участка интерфейса при обновлениях в данных — скажем, какой-нибудь список лайков к посту — React сделает инкрементальный апдейт. При том, что в коде это будет выглядеть столь же абсурдно просто. Это реально очень круто и гигантский шаг вперед. Примерно такой же гигантский, как концепция «отложенных значений» в асинхронном программировании, или как статический анализ типов. Это нельзя игнорировать, потому что это позволяет наконец-то сфокусироваться на продукте — а не на том, как мы его делаем — полностью абстрагируя вас от этой скучной хреноты.


Но как же он, блин, это делает?


В нашем проекте я пытался решить эту задачу с помощью концепции channels или «динамических значений» — расширяя идею «отложенных значений». Мне даже удалось сделать примитив, интерфейсно похожий на Promise и обладающий совместимой с ним семантикой — но позволяющий проталкивать последующие изменения значения по цепочке. Представьте, что label и toggled в предыдущем выражении это не просто значения, а некие «потоки значений», каналы. И наши методы работы с DOM понимают такие типы значений, создавая биндинги. То есть, setAttribute ('toggled', toggled) не просто однократно изменит значение атрибута, но создаст связь, позволяющую интерактивно обновлять атрибут при изменениях связанного с ним значения. Всю программу в таком случае можно рассмотреть как вычислительный граф, по которому пушатся изменения. Для таких динамических значений можно написать библиотеку алгоритмов типа map, filter, reduce и работать с ними на прикладном уровне как с обычными значениями — с массой оговорок… ведь для таких «динамических значений» в языке нет специального «сахара», аналогичного async/await для промисов. И всё это становится очень нетривиально, когда речь заходит об обновлениях сложных структур данных — поэтому я ограничился лишь частными случаями. В результате, итоговый код на прикладном уровне был всё так же очень далек от идеала «чистых функций» — немалую его часть по-прежнему занимало разруливание задачи обновления интерфейса. Но это все равно был большой шаг вперед по сравнению с полностью «ручным управлением».


Так и что же React? Так вот, он устроен нифига не так! Это-то меня и поразило, потому что я настолько заморочился по observables, что даже не предполагал, что может существовать и более простое (для пользователя) — и менее инвазивное решение, не требующее модификации семантики работы со значениями и специальной библиотеки алгоритмов над ними.


Попробуем воссоздать ход мыслей разработчиков React, исходя из двух простых изначальных тезисов:


  1. Компоненты это чистые функции — которые не только выглядят так, но и реально являются ими
  2. Они параметризуются чистыми данными, без каких-либо observable-оберток — то есть, их не нужно «разыменовывать»

Это значит, что все наши данные хранятся в большом тупом объекте, типа:


data = { todos: [ { toggled: true, label: 'Тудушка' }, { toggled: false, label: 'Ещё тудушка' } ] }

А интерфейс представляет из себя алгоритм сопоставления частей этого объекта с DOM-конструкциями:


TodoList = ({ todos }) =>
    <h1>Тудушки</h1>
    <content>{ todos.map (ToggleButton) }</content>

И вывод его на экран выглядит так:


render (TodoList (data)) // а-ля console.log

Очевидно, что вся «магия» должна происходить в render. При этом всём функции вроде ToggleButton и TodoList не должны возвращать реальный DOM — потому что они реально вызываются каждый раз при изменениях в данных. Если у какой-то тудушки поменяется свойство toggled, то функция ToggleButton будет вызвана заново — и она вернет новый результат. Но если это не DOM, то что? Вместо этого функции в React возвращают легкий "blueprint" (в React это называется Virtual DOM) — лишь описывающий тот DOM, который мы хотели бы увидеть на экране — но не являющийся им реально. Что-то типа:


{ type: 'button', props: { toggled: true, children: ['Тудушка'] } }

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


{ type: 'button', props: { toggled: true, children: ['Тудушка'] } }

{ type: 'button', props: { toggled: false, children: ['Тудушка'] } }

… выразится в действии setAttribute ('toggled', false) на соответствующем DOM узле. Смысл в том, что создавать JavaScript-объекты в сотни и тысячи раз быстрее, чем создавать DOM узлы. Поэтому даже со сравнениями и вычислением разницы — это уже быстрее, чем просто генерить новый DOM.


React очень буквальным образом пытается решить задачу обновления интерфейса при изменениях в данных: он буквально вычисляет эти обновления как разницу между новыми данными и старыми данными. Делая это на всём интерфейсе сразу. И как же он умудряется делать это быстро?


Сначала подход React показался мне чем-то вульгарным, по двум причинам:


  1. Проблема «перерендера всего» при малых изменениях в данных вроде бы не решается, а просто «прячется под ковёр». Ведь мы всё так же при изменении галочки на тудушке вынуждены заново генерить интерфейс. Просто теперь это не DOM, а легковесный VDOM, но проблема-то та же — что если у нас настолько тяжелый интерфейс, что даже этот VDOM сгенерировать из данных окажется слишком ресурсоёмкой задачей?


  2. Дифф между старым и новым VDOM считается точно таким же образом — по всему VDOM, для всего интерфейса, даже если у нас поменялась какая-то маленькая хренюлина из тысяч. Разве это может быть быстро?

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


Во-первых, diff считается очень быстро благодаря всего нескольким простым эвристикам — за линейное время от количества узлов. Но что если узлов много? Все же нехорошо выполнять 10000 операций сравнения чтобы выяснить, что у нас поменялся один булевый флаг.


Но ведь нам не обязательно сравнивать узлы в VDOM по значению — нам достаточно сравнить ссылки, чтобы отсечь неизменившиеся поддеревья. И наши рендеринг-функции (TodoList, ToggleButton) не обязаны каждый раз возвращать новый VDOM-объект — они могут возвращать старую ссылку на VDOM, если инпут не изменился — не генеря новый объект. Этот механизм называется мемоизация. Таким образом, если мы поменяем что-то в глубине VDOM, то функции сравнения при обходе будут «забуриваться» только в те поддеревья, где произошли изменения — отбрасывая неизменившиеся, просто сравнив ссылки на них. Ведь если ссылка не поменялась, то и внутри ничего не поменялось — незачем туда залезать. Разумеется, эта мощная эвристика работает только с чистыми функциями, не имеющими побочных эффектов — гарантирующими неизменность результата и однозначность соответствия его входным параметрам. Вот почему функциональная чистота так важна для хорошей производительности! Хотя это и контринтуитивно на первый взгляд — ведь функциональный код обычно наоборот ассоциируется с плохой производительностью и избыточными действиями (на низком уровне). Тогда как именно функциональный подход и позволяет от избыточных действий (на высоком уровне) эффективно избавится в данном случае. И выигрыш от этого перекрывает пенальти от низкоуровневой избыточности на порядки! Вот какой оксюморон — просто мы не видим леса за деревьями обычно...


Но как нам эффективно мемоизовать эти наши ToggleButton? То есть, у нас на входе кучка данных, и как понять, изменились они или нет? Нужно ли считать новый VDOM, или вернуть старую ссылку? Гы. Вы ведь уже догадались, что это та же самая задача, что и с вычислением разницы между VDOM? И решение здесь то же самое — просто сравнивать ссылки! Но из этого следует очень важный вывод...


Наши данные должны быть иммутабельные


Подобно результирующему VDOM, чтобы убер-эффективный механизм вычисления разницы через сравнения ссылок мог работать — нам нужно, чтобы эти объекты были неизменяемые. То есть, мы не можем просто залезть внутрь массива данных и сделать todos[1].toggled = true — это скомпрометирует весь наш механизм сравнения ссылок. Ведь, в таком случае, функция TodoList посчитает, что todos не изменились — раз не изменилась ссылка на них — и ничего не будет перерендерено. Поэтому если мы хотим «залезать» внутрь данных и менять внутренности как вздумается, то придется отказаться от сравнения ссылок и мемоизации. И это приведет к тому, что нам придется перерендеривать весь VDOM при изменениях в одной галочке! Теперь вы должны понимать, почему иммутабельность это не какая-то «прихоть» функциональных языков — а ключевое и необходимое условие для того, чтобы этот подход вообще мог работать и быть эффективным.


Иммутабельные структуры данных только на первый взгляд кажутся неэффективными по сравнению с обычными массивами и словарями — на деле они дают гигантский прирост производительности — просто не в тех задачах, в которых вы применяете обычные мутабельные структуры… Но за это приходится расплачиваться некоторыми неудобствами на прикладном уровне, скажем, мы не можем пользоваться оператором присваивания свойству [1].toggled = true, вместо этого придется сделать что-то вроде:


todos = todos.set (1, Object.assign ({}, todos[1], { toggled: true }))

Где операция .set вернет нам новый массив — где все элементы останутся прежними, кроме того, что под индексом 1 — тот будет заменен на копию прежнего, но с другим значением свойства. Это позволит процедурам рендеринга быстро найти именно это изменение, переведя его в соответствующую операцию с DOM — просто пробежавшись по ссылкам.


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


Разумеется, для облегчения работы с иммутабельными структурами есть готовая библиотека, она называется Immutable.js. Что интересно, в JavaScript даже есть возможность запретить произвольному объекту меняться — Object.freeze — исключив, таким образом, возможные ошибки связанные со случайным изменением таких коллекций.


На самом деле, с помощью нового механизма Proxy Objects, недавно появившегося в JavaScript, можно реализовать предыдущую конструкцию таким образом, цепочечно перехватывая обращения к свойствам:


todos = todos[1].toggled.set (true) // вернет новый todos

Так что это синтаксически почти не будет отличаться от «мутабельного» доступа!


Redux


До того момента, как я осознал глубинную роль неизменяемости данных в проблеме обновления интерфейса, Redux был для меня чем-то малопонятным. Он интриговал меня тем, что с его помощью легко достичь вещей вроде сохранения истории изменений и undo/redo — это как раз та задача, которую мы пытались решить в прошлом году для нашего проекта — но мне был непонятен весь этот хайп вокруг этого — как и то, почему это почти всегда упоминается рядом с React.


Но ведь если вдуматься в то, что мы осмыслили про React и то, как это обуславливает паттерны работы с нашими данными — то из осознания этого естественным образом вытекает то, чем является Redux. Итак, поскольку:


  1. Операции над данными — чистые функции (previous state > new state)
  2. Операции изменения данных (бизнес-логика) обычно изолируют от интерфейса

То выходит, что наш обособленный от интерфейса набор операций это что-то вот такое:


addTodo (oldState, label) => ({ todos: oldState.todos.concat ({ label: label, checked: false }) })
checkTodo (oldState, index) => ({ todos: ...

И прикрутив к этому динамический диспатчинг метода (ну например чтобы по сети вызовы передавать, или в историю сохранять), получаем Redux:


reduce (oldState, action) => {
    switch (action.type) {
        case ADD_TODO: return { todos: oldState.todos.concat ({ label: action.label, checked: false }
        case CHECK_TODO: return { ...

Как видите, всё очень просто… наверное.


Вывод


Возможно, классы все-таки действительно не нужны — и можно обойтись одними чистыми функциями. Однако, в этой заметке я описал «идеализированный» React — такой, каким он стремится быть — тогда как в реальном React есть и классы, и состояния. Потому что иногда все же нужно хранить какое-то локальное состояние, или как-то вручную что-то контролировать в жизненном цикле DOM узлов. Совершенно непонятно также, как в «чисто функциональном» подходе предлагается решать проблему анимаций. Сейчас я читаю статью React Fiber Architecture, которая появилась буквально на днях, и описывает какой-то их супер-новый подход, над которым они работали последние два года, который якобы эту проблему решает. Но пока я не продвинулся дальше первых абзацев, едва осилив раздел Prerequisites — в результате осмысления которых у меня и родилась эта статья.

Поделиться с друзьями
-->

Комментарии (136)


  1. webmasterx
    20.08.2016 14:36

    Странно что почитали про Redux, но забыли про RxJS.


    1. alex_blank
      20.08.2016 14:51

      Я про него не забыл, просто у нас в проекте как раз подобная абстракция используется для событийной модели и асинхронного dataflow — но для меня было совершенной загадкой, как React'у удается быстро обновлять DOM при изменениях в данных без использования подобной абстракции. Для меня это было инсайтом — как и то, как тесно это связано с иммутабельностью и функциональной чистотой.


      1. babylon
        20.08.2016 16:22
        -1

        @Возможно, классы все-таки действительно не нужны@
        Это вы моих комментариев начитались:) Как всё таки React'у удается быстро обновлять DOM при изменениях в данных без использования подобной абстракции.@? Тема не раскрыта…


        1. SbWereWolf
          21.08.2016 00:57
          +1

          тема раскрыта, любое изменение это всегда изменение ссылки, смотрим где поменялись ссылки — там перерисовываем.


          1. babylon
            21.08.2016 10:54
            -2

            Какое изменение ссылок??? Вы о чём??? Как данные влияют на структуру?:)


  1. a553
    20.08.2016 14:39
    -6

    Вот за такой GNU стиль расстановки пробелов надо убивать.
    $static ($debounce ($bindable ($memoize (function () { ... })))),


    1. alex_blank
      20.08.2016 14:42

      Я просто думаю что правила типографики из текстов на естественных языках применимы и к коду — ведь текстовое представление кода именно на естественных языках и базируется. То есть если вы в английском или русском языке перед скобками ставите пробел, то почему бы и в коде так не делать? Это очень естественно.

      Убивать вообще никого не надо, агрессия нынче не в моде.


    1. A-Stahl
      20.08.2016 14:52
      +8

      Предлагаю запретить фиолетовые фломастеры. Они отвратительны. Зелёные намного вкуснее.


    1. ganqqwerty
      20.08.2016 17:23
      +6

      Так, а что не так-то? Надо убрать пробелы? Добавить еще пробелов? Не очень понял, что не понравилось вам.


  1. ganqqwerty
    20.08.2016 14:40
    +6

    Небольшой вывод по прошлой статье и текущей (поправьте меня, если в моей логике есть брешь): в js мода меняется быстрее чем в индустрии женских сумочек. Поэтому если вы хотите суметь устроиться на работу через пару лет, вам стоит сейчас выбирать конторы, которые запускают много маленьких проектов. В каждом новом проекте можно пробовать ту технологию, которая сейчас в моде. Очень странная ситуация, программисты, которые работают над действительно большими и нужными проектами (а поэтому с большим количеством legacy) оказываются в проигрышной ситуации.


    1. alex_blank
      20.08.2016 16:22
      +3

      Мой друг как-то рассказывал мне историю про своего кореша, который ушел из Яндекса по той причине, что опыт, полученный в Яндексе был применим только в самом Яндексе.


      1. ganqqwerty
        20.08.2016 16:54
        +5

        при этом если мы нанимаем кого-то в проект — все равно спрашиваем не про либы и фреймворки, а чем this.x отличается от prototype.x и как работают замыкания. И вот ведь незадача! — часто человек весь из себя редукс, а на такие вопросы не отвечает.


        1. alex_blank
          20.08.2016 17:02
          +1

          Мне чтобы для себя ответить на эти вопросы, пришлось много лет делать свой фреймворк. Я об этом не жалею, потому что это позволило мне разобраться в JavaScript вплоть до самых его кишок. Но именно поэтому я не осуждаю людей, которые не знают что такое .prototype или зачем нужен Object.create — потому что понимаю, сколько надо потратить личного времени, чтобы в этих деталях разобраться. У большинства людей на работе просто нет задач, которые это требуют.


          При этом, при выборе сотрудника на работу я действительно отдам предпочтение тому, кто разбирается в используемых инструментах на самом низком уровне. Возможно, это просто предрассудок и суеверие, не знаю.


          1. ganqqwerty
            20.08.2016 17:18
            +1

            Возможно, это просто предрассудок и суеверие

            Нет, это принцип «смотреть на пару уровней абстракции вниз», старина Джоэл про него писал в статье «Дырявые абстракции».


          1. Zibx
            20.08.2016 22:27
            +1

            А что там ещё в языке остаётся кроме скоупов? Во всём языке не больше пяти абстракций.


            1. alex_blank
              20.08.2016 22:45

              Язык же не висит в сферическом вакууме отдельно от его реализации и от стандартной библиотеки, тонкостей очень много. Да и в самом языке странностей хватает вообще-то. Где-то был пост с хорошей подборкой их, я запамятовал.


              1. zorro1211
                21.08.2016 22:50

                Вот 2 теста замечательных на знание языка. Моя практика показывает что с ребятами которые хуже его проходят, сложнее работать и наоборот. Был один случай где человек хоть и не знал как конкретно язык работает, все равно не сдавался и выдавал предположения, которые были логичны и один из вариантов был верным. В данный момент он один из лучших разработчиков у нас.


                1. http://perfectionkills.com/javascript-quiz/
                2. http://perfectionkills.com/javascript-quiz-es6/


        1. Fen1kz
          21.08.2016 04:28

          Покажете такого человека? А то все ими грозите, а на практике не встречал людей кто не знает про прототипы, но хорошо знает redux


          1. zorro1211
            21.08.2016 22:52

            Показать не смогу, но был человек на интервью который хорошо знал api angular, но пройти хотя бы на 70% http://perfectionkills.com/javascript-quiz/ так и не смог.


            1. Fen1kz
              21.08.2016 23:11
              +2

              Там ни слова про прототипы, в основном вопросы на внимание к typeof. Ну и, естественно, как там чувак сказал в комментах: "if these scenarios… come up for you in practice quite often, then you are doing it wrong."


            1. ganqqwerty
              22.08.2016 12:09

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


              1. zorro1211
                22.08.2016 15:51

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


            1. ganqqwerty
              22.08.2016 12:14

              Вот например на этот вопрос единственным правильным ответом будет «Мне кажется, что тот, кто это писал — мудак, надо брать справочник и думать, как переделать эти спагетти»:

              (function f(){
                  function f(){ return 1; }
                  return f();
                  function f(){ return 2; }
                })();
              


              1. alex_blank
                22.08.2016 12:21

                Тест как раз нормальный, реалистичный. Потому что на большинстве работ вы будете заняты как раз тем, что будете переписывать легаси код, в котором вот такого будет полно. Вы конечно можете всегда найти работу в светлом и чистом стартапе (чтобы потом в будущем кто-нибудь точно так же чесал репу над вашим кодом), но надо понимать реалии рынка — в мире есть очень много плохо написанного кода, который нужно поддерживать или хотя бы переписать. А для этого нужно как минимум уметь в сортах говна разбираться.


                Как пел Кровосток, «в наше время, в нашем месте, стоит разбираться в жести...»


                1. PsyHaSTe
                  22.08.2016 19:21

                  Я понимаю, суровый энтерпрайз, все дела, но мне кажется даже самый упоротый программист который закончил 7 классов общего образования не будет писать в таком стиле:

                  var x = 1;
                    if (function f(){}) {
                      x += typeof f;
                    }
                    x;
                  


                  1. alex_blank
                    22.08.2016 19:26

                    Это всего лишь редуцированные до абсурда примеры, поэтому они и кажутся абсурдом. На деле за каждым таким примерчиком стоит какой-то очень сложный и заковыристый баг из реальной практики. Который, если свести его к минимальному примеру, даст вот такой, кажущийся очевидной глупостью, сниппет кода.


                    1. PsyHaSTe
                      22.08.2016 19:56

                      После статически типизированных языков это кажется бредом. из 4 строчек кода 1 является объявлением, а еще в 3 в нормальном ЯП была бы ошибка.

                      • Cannot implicitly convert type 'void' to 'bool', либо Not all code paths return a value
                      • Operator '+' cannot be applied to operands of type 'int' and 'Type'
                      • Only assignment, call, increment, decrement, and new object expressions can be used as a statement


                      Я понимаю, что возможно там удалено много кода, и на самом деле это разбавлено кучей кода, который не оказывает влияния на поведение, но делает ошибку менее заметной, но блин яхз как можно не заметить сразу все вышеперечисленное. И ладно, я допускаю, что такое может случиться в продакшне, но считать это тестом по ЯП это я просто не знаю. Могу предположить, что где-то встречаются индивиды, которые оставили после себя на поддержке подобные перлы, и для отбора кандидатов на саппорт в эти самые места можно было бы использовать такие тесты, но он позиционируется как мейнстрим…


      1. RomanYakimchuk
        20.08.2016 17:11
        +1

        Они работают по БЭМ методологии. В технологиях это не ограничивает, но, насколько я знаю, методология вынуждает использовать весь БЭМ стек, хочешь ты или нет, иначе будешь много грабель собирать. Это не плохо, но такие навыки применимы как-правило только в крупных компаниях: Mail.ru, Яндекс, Rambler, и другие, которые работают по БЭМ.

        Ребята работающие с БЭМ, будут интересны ваши комментарии по поводу смены места работы, есть ли сложности с этим.


        1. ganqqwerty
          20.08.2016 17:20
          +1

          нашел документацию, захватывающая методология!


          1. RomanYakimchuk
            20.08.2016 17:32

            Яндекс очень активно ее продвигает, по-возможности.

            Можно прийти на их мероприятия, и Я-Субботники, при желании.
            https://events.yandex.ru/events/bemup/
            https://events.yandex.ru/events/yasubbotnik/

            Правда, не знаю проводят ли они еще их.


      1. SbWereWolf
        21.08.2016 01:00
        +1

        +1 уволился с любимой работы ( любил её пять лет ), только потому что опыт который там получал, ни где больше спросом не пользовался.


    1. ko11ega
      20.08.2016 16:56
      +12

      Для больших нужных проектов есть другие технологии. Отличные от JS. Со строгой типизацией и нормальными классами. В которых во главе угла не мода, а читаемость, понятность кода, единообразие стандартных решений. Что позволяет годами поддерживать продукт с >100K LOC, просто и комфортно рефакторя старый код, а не подрываясь на "а давайте перепишем все на этой новой и блестящей свистоперделке".


      1. ganqqwerty
        20.08.2016 17:22
        -1

        В любом веб-проекте есть js. А по вашим словам выходит, что веб-проекты не могут быть большими и нужными.


        1. ko11ega
          20.08.2016 17:55
          -1

          Еще как могут )) Вам сюда Dart


          1. staticlab
            20.08.2016 19:17
            +2

            Вот честно и без обид. Не надоело ещё его в каждом треде рекламировать? :)


            1. ko11ega
              20.08.2016 19:50
              -1

              Честно и без обид: надоело читать как в каждом треде по JS люди жалуются о том как им тяжело живется и на проблемы у которых есть удачное решение. Которым они не пользуются в силу того, что им "религия" и "стадный инстинкт" не позволяют.


              1. franzose
                21.08.2016 03:52
                +3

                Ваше поклонение Dart тоже своего рода «религия».


          1. Kolonist
            20.08.2016 23:31
            +9

            К вопросу о «новых и блестящих свистоперделках» ;)


          1. Odrin
            22.08.2016 12:08

            Извините, но даже Google предпочел TypeScript Dart'у.


  1. andybelo
    20.08.2016 15:22
    +1

    Считаю, что главное в реакте решение проблемы рендеринга, то же самое в более общем смысле сделали в библиотеке TStream java8. Там можно это всё распараллелить. Прикол в том, что в Windows эта проблема не решена за столько лет. При загрузке ОС она перерисовывает десктоп много раз.


  1. SDSWanderer
    20.08.2016 16:42

    deleted


  1. AndreyRubankov
    20.08.2016 18:58

    Меня всегда удивлял этот странный путь развития JS (ES).

    Все js разработчики всегда плюются в сторону классов и типов, но при этом лучшие из них создают свою собственную систему классов / типов, а V8 под капотом типизирует создаваемые объекты.
    Некоторые и вовсе собрались и создали TypeScript. Казалось бы крутая штука! Но как и все, что делается под эгидой Microsoft — сделано криво.

    Дошло до того, что в ES2015 все же ввели классы, но не ввели типизацию (надеюсь, еще одумаются), теперь половина плюется, половина с радостью использует. Классы на самом деле хороши, но нету модификаторов доступа (public, private), которая иногда очень даже нужна. Нету полей класса, их можно объявить только через конструктор.

    Почему все крутые чуваки в мире JS (ES) всегда стараются изобрести велосипед?

    Вот уже много лет есть готовый язык, который основан на базе ES5, в котором есть Классы, Декораторы, Spread syntax, Поля классов, Модификаторы доступа, Интерфейсы. При этом язык позволяет создавать и использовать анонимные объекты и функции как в ES5, позволяет не описывать типы у параметров и возвращаемых значений, позволяет принимать любые значени (т.е. писать в ES5 стиле).
    ActionScript3 — язык созданный на базе ES5, для Flash. Но сам синтаксис очень вкусный и без проблем подошел бы как путь развития синтаксиса ES.


    1. AndreyRubankov
      20.08.2016 19:12

      Вернее, не Декораторы, а мета-теги (аннотации). Декораторы из ES имеют все же более мощный синтаксис.

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


      1. gibson_dev
        20.08.2016 19:32
        +3

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


        1. alex_blank
          20.08.2016 19:51
          +3

          Классы это просто удобная декларативная форма конструирования прототипов, ничего плохого в этой абстракции нет per se. Плоха именно ES6 реализация, которая ломает идею «прототипы это тоже объекты» на уровне синтаксиса. То есть было:


          Foo = $class ({ methodA: ..., methodB: ... })

          стало:


          Foo = class { methodA () { .. } methodB () { ... } }

          Это даунгрейд по сравнению с ES5, по той причине, что прототип синтаксически перестал быть объектом, и я не могу переопределить реализацию class, добавив какой-нибудь препроцессинг этого. Для этого появился костыль в виде декораторов:


          Foo = @decorate class { ... }

          Но — ха-ха — это все равно даунгрейд, потому что внутри { ... } у нас не объект. Там другой синтаксис, который является подмножеством JSON — не позволяя делать key: value, а значит, теряя в выразительности.


          Никакого смысла в ключевом слове class нет — оно не решает ни одну проблему, но создает кучу проблем. Все плюшки классового синтаксиса доступны и в объектах. Вот смотрите:


          obj = {
              method () { },
              get prop () { },
              foo: bar
          }

          Видите? Я могу так же коротко записывать методы и акцессоры, но при этом я не теряю возможности сделать foo: bar. В ES6 достаточно было сделать лишь это, не было никакой нужды вводит ключевое слово class.


          Проприетарные системы классов в JS просто выглядят так, как будто они пытаются имитировать классы. На деле они дают намного больше чем классы, и по факту являются просто абстракцией, позволяющей удобно описывать прототипы. И эти системы потому у каждого фреймворка свои, потому что у каждого решают какие-то свои задачи, специфичные для фреймворка. Скажем, где-то есть миксины, где-то нет. Ключевое слово class все ломает, запрещая разработчикам как-то встраиваться в механизм создания прототипа, фиксируя этот механизм, обрубая все замечательные возможности, которые появились в различных фреймворках.


          Когда разработчики стандарта JS осознали, что же они натворили — бегом побежали делать декораторы, которые эту проблему немного позволяют решить. Но правильным шагом было бы исключить class из стандарта (сделать его deprecated), и развивать идею декораторов для объектов. Оставив задачу конструирования прототипов в userland. Потому что там нужна гибкость, и классы в ES6 эту гибкость уничтожили.


          1. alex_blank
            20.08.2016 20:00

            Брр, не подмножеством, конечно же, а вообще несовместимой с ним фигней, усложняющей язык.


          1. AndreyRubankov
            20.08.2016 20:40

            Ниже я уже ответил, что не DOM-манипуляцией едины. Есть желание писать действительно большие системы на js. Системы, которые позволили бы полностью заменить большинство нативных приложений. Но столь огромные приложения очень тяжело поддерживать, если нету системы классов и типов.

            Класс из ES2015 (принято же так называть, а не ES6) действительно не столь хорош, каким бы мог бы быть. Я выше приводил пример языка, который был бы отличным путем развития.

            class Foo {
               private var _startPoint: {x: 5, y:10};
               private var _callback:Function;
            
               function set startPoint(startPoint) { this._startPoint = startPoint; }
               function get startPoint() { return _startPoint; }
            
               function set callback(func:Function) { _callback = func; }
            }
            


            Это лишь для демонстрации синтаксиса. Как видно, то, что вам не нравится в ES2015 в том же as3 реализовано так, как вам хотелось бы. Хотите — пишите в ES5, хотите пишите в виде ООП.
            Т.к. это все же ES, никто не отменяет использование прототипов, можно уже существующим объектам подсунуть миксин.


            1. alex_blank
              20.08.2016 21:12
              +1

              Да, я согласен с тем, что классы (как сущность в синтаксисе) нужны для того чтобы статическим анализаторам (системе типов) облегчать работу. То есть в данном случае это просто контракт типа, который говорит что «а вот у нас такие методы есть», и это сразу доступно сразу после разбора AST, без необходимости делать глубокий вывод типов, как это делают инструменты типа Tern. Документацию опять же генерить легче.


              Аннотации типов (и их статический анализ) в JS сейчас уже вовсю появляются — в статье про это упомянуто (Flow). Так что это может быть внешним инструментом.


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


          1. serf
            20.08.2016 23:20

            Потому что там нужна гибкость, и классы в ES6 эту гибкость уничтожили.

            Что именно классы уничтожли? Что мешает их не использовать если они вам не подходят?


            1. alex_blank
              20.08.2016 23:40

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


              Пока что мне вот подсказали, что для статического анализа типов классы хорошо, как хинт. Это действительно так, наверное.


              1. serf
                21.08.2016 00:23
                -1

                Пока что мне вот подсказали, что для статического анализа типов классы хорошо, как хинт.

                Любому программисту это должно быть очевидно.

                и при этом была намного гибче

                Вот как раз на «уровне библиотек» и «гибче» не позволяет использовать преимущество классов — жесткая и однозначно интерпритируемая/анализируемая структура.

                Не стоит слепо повторять чужие слова, об «уничтожили» и тому подобное.


                1. alex_blank
                  21.08.2016 01:09

                  Какие чужие? Это мои слова, и это основано на моём опыте. Я как раз делал систему классов и декораторов для ES5, и вижу какие напряги возникают с попытками перенести это на нативные ES6 классы. Я не готов отказываться от этого только для статического анализа типов, это совершенно неразумный tradeoff в моем случае.


                  Просто вы видимо пришли из мира Java и пытаетесь писать код в таком же стиле. Разумеется, в таком случае JavaScript покажется каким-то ущербным Java без системы строгой статической типизации. Но это просто потому что вы не понимаете фишки JS.


                  1. serf
                    21.08.2016 01:13

                    Но это просто потому что вы не понимаете фишки JS.

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


                    1. alex_blank
                      21.08.2016 02:04

                      Согласен, я погорячился с выводами. Просто я часто замечаю, что люди пытаются сделать из JS какой-то Java, хотя куда правильнее было бы двигаться в сторону функциональных вещей — и таким образом достигать статической верифицируемости. Собственно, я в этой статье и изложил видение того, как этого можно было бы достичь. А прототипы и динамичность языка мне нравятся именно возможностями метапрограммирования (embedded DSL), и это не очень пока совместимо с Java-подходом. Java это такой энтерпрайз-фашизм, а в JS есть дух хардкора, молодости и эксперимента. И вот это я и называю «фишкой».


                      1. AndreyRubankov
                        21.08.2016 23:48

                        > Java это такой энтерпрайз-фашизм

                        Это было очень сильное заявление. Оно очень точно описывает все программные продукты от IBM.

                        В java «хардкора» не меньше, чем в js, он просто другой =)
                        но обсуждение этого топика скатится в холивар)))

                        Как я писал ниже, есть настроения писать серьезные приложения на js с большими объемами логики (фактически заменить нативные приложения). Эти приложения должны быть Легкими в поддержке и Быстрыми в изучении.

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

                        Но в целом да, можно сказать: «не хочу в такой проект, я хочу что-то классное!»


                  1. TheShock
                    22.08.2016 05:21
                    +9

                    Просто вы видимо пришли из мира Java и пытаетесь писать код в таком же стиле. Разумеется, в таком случае JavaScript покажется каким-то ущербным Java без системы строгой статической типизации. Но это просто потому что вы не понимаете фишки JS.

                    Я уже почти десять лет как JS-программист, и пришел в него далеко не из джавы. Я писал правда большие вещи, библиотеки, игры, которые активно развивались и успешно поддерживались годами. Это не каждые полгода новый сайтик на 50 табличек сверстать. Статическая типизация — это не wish, это необходимость для любого крупного проекта, который не должен быть заброшен через месяц после релиза и у которого стоят высокие требования к качеству. Мы к этому пришли не с «Джавы», а кровью, разгребая динамическую типизацию. Хотя еще пять лет назад я был уверен, что утиной типизации — хватает:
                    Типизация — утиная, JavaScript не содержит подводных камней, просто люди не могут его понять


                    Декораторы — и вполовину не дают всего необходимого. Именно потому у них там от силы memoize, debounce и т.п. заваливается. Тот же memoize вообще бесконтрольный — плюет в память неограниченно и навсегда, да и невозможно вменяемо почистить от устаревших данных. Как покрыть тестами функцию, на которую наложили debounce?

                    Я уж молчу об оптимизации. Я понимаю, что современным хипстерам срать на нее хотелось и они готовы использовать O(n2) вместо O(n) исключительно для понтов, чтобы функцию можно было назвать чистой, пол-стейта приложения пересоздается ради того чтобы изменить три поля, МусороСборщик не хипстер, все сдержит, а функция заворачивается в функцию в функции, которую вызывает функция через функцию, в результате стек-трейс не влезает в экран. Но лично мы по-максимуму отказались от замыканий (постарались вынести их в момент инициализации игры или заменить на циклы, а эвенты — это методы обьектов) и этим +30% производительности игре добавили. А это, кстати, 60 фпс вместо 45. А это еще мы не использовали всякие новомодные «давайте 100 раз пересоздадим объект для того, чтобы сделать элементарную операцию».

                    А операция такая. У вас 10000 объектов, которые приходят с сервера. У них есть свойство, например Группа. Необходимо сгруппировать их по этому свойству в массивы. Вот процедурный код:
                    (objects) {
                      let result = {};
                      for (let obj of objects) {
                        if (result[obj.group] == null) {
                          result[obj.group] = [ obj ];
                        } else {
                          result[obj.group].push( obj );
                        }
                      }
                      return result;
                    }
                    


                    Как написать новомодно?
                    (objects) {
                      return objects.reduce((result, obj) => ({
                        ...result,
                        [obj.group]: [
                          ...(result[obj.group] || []),
                          obj
                        ]
                      }), {});
                    }
                    


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

                    Я тоже делал систему классов в далеком 2010-м. И я ее делал по той простой причине, что не существовало такой системы, которая поддерживала б современные на тот момент технологии, например аксессоры(get, set). Да, современные классы далеко не идеальны. Они очень далеки от идеала. Как и деструктуризация, которая имеет другой синтаксис в сравнении с импортом. Но ключевое слово «класс» нужно. Чтобы движки нормально оптимизировали инстанциирование, без костылей с иннер-классами, чтобы проверять ошибки на этапе компиляции (а для этого еще нужна типизация, пусть и опциональная), чтобы иметь нормальное автодополнение в IDE, когда оно не вываливает мне на голову тонну всего, что похоже и чтобы IDE подсвечивала, когда я ошибаюсь. Чтобы было нормальное логирование в консоли, где я видел бы, что именно представляет из себя этот объект. Чтобы, когда я открываю чужой код, который писали 6 месяцев назад и вижу там аргумент config мне не приходилось использовать телепатию, звать того человека или ставить логирование, запускать код, смотреть что пришло, а потом на продакшене выяснить, что оно еще, в другом месте может получить совершенно другие данные, которые проходят утиную типизацию в старой логике, но не проходят в новой. Я уж молчу, что классы тестами покрываются очень легко — есть методы, есть состояния, которые можно проверить. Когда ж человек, который думает, что он пишет на ФП начинает комбинировать функции и использовать функции, которые возвращают функции — это покрывать тестами черт ногу сломит. Самый простой пример — React.PropTypes.shape возвращает функцию, которая не валидирует вне своего контекста. Замыкания по сути выполняют роль классов, только ты в классах явно описываешь стейт, а замыкание — какой стейт захочешь — такой и захватишь, при этом он не логируется в консоли, плохо чистится сборщиком мусора и совершенно неявный.

                    проверять контракты типов для свойств в JavaScript — так что наш ToggleButton мог бы выглядеть так:

                    ToggleButton = ({ label : String, toggled = false }) => <button toggled="{toggled}">{label}</button>
                    


                    И при этом обязательность и тип свойства label проверялись бы еще до запуска программы!

                    Нет, не получится, в деструктуризации нельзя указывать типы. Тут обсуждаем варианты:
                    https://github.com/facebook/flow/issues/235

                    В свете этого, «классовая» запись выглядит просто как кривой бессмысленный костыль — и оно бы таким и было, если бы не один важный момент

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

                    И да, механика классов в ЖС пока малоюзабельна из-за ряда проблем, но это не причина отказываться от них и их развития в будущем, как говорит один из тех хипстеров по ссылкам, которые дал автор.


                    1. alex_blank
                      22.08.2016 09:59

                      Про то что Flow не умеет в типизацию destructuring-выражений это, конечно, печально — но думаю это всего лишь временная неувязка. По поводу ваших сетований об алгоритмической сложности «хипстерского» (wtf) кода — вы же должны понимать, что это лишь специфика вашей работы (игры?), связанной с повышенными требованиями к производительности.


                      В достаточно сложном проекте (ERP предприятия), работающем с кучей данных (сотни тысяч записей в таблицах, обрабатываемых на client-side) мне пришлось всего пару раз вручную написать цикл — это собственно, табличный контрол который осуществлял виртуализацию рендеринга и фильтрацию/сортировку датасетов. Во всем остальном, «прикладом» коде я с радостью использовал все прелести функциональных выражений, вообще не думая о том, насколько быстро они работают (покуда это не начинало тормозить).


                      Premature optimization is the root of all evil, как говорится. Я в JS пришел из C++ и дико рад, что мне в моих повседневных задачах больше не надо думать о таких вещах, как ручной менеджмент памяти или там, упаси боже, какие-нибудь cache misses. Я хочу забыть про это, как про страшный сон, и заниматься вещами поинтереснее, чем бессонными ночами думать о «протекающих абстракциях» в моем коде. Покуда он работает и работает приемлемо — я не хочу об этом думать.


                    1. serf
                      23.08.2016 01:48
                      -3

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


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


        1. AndreyRubankov
          20.08.2016 20:21
          +1

          Я не возражаю против этого, у js действительно свой увлекательный путь, который нужно полюбить.

          Но! Почему-то миллионы леменгов хотят принести ООП и систему классов. Стоит посмотреть на старые библиотеки / фреймворки. Каждая из них вводит свой вариант создания Класса. jQuery, Backbone, Lodash, у них у всех свой путь к созданию эдакой системы типов. «Более современные» идут тем же путем.
          Ответственные за развитие ES вводят классы. Может не все так хорошо с этим путем?

          И ведь это все мы сейчас смотрим только с точки зрения веба (js для работы с DOM), тут и вправду можно обойтись ES5 и компонентным подходом к написанию кода.

          Но кроме как для работы с DOM js используется еще и на бекенде (node.js | express.js). На js сейчас пишутся огромные веб-приложения с кучей бизнес логики (не только манипуляция с DOM). А еще игры пишут на js.
          И вот для больших приложений и игр без объектной модели довольно тяжело будет обеспечивать поддержку.

          Вот представим, что у нас есть Quake, написанный на js (Quake — это очень маленькое, по объемам логики, приложение). И у нас есть ошибка, конкретную точку, где она проявляется — мы знаем. Как найти все пути, которые приводят к этой точке кода?


          1. zorro1211
            21.08.2016 23:39

            Спасибо за комментарий, я пытаюсь в этой теме разобраться и у меня есть небольшой фидбэк к вашему комментарию:


            Но! Почему-то миллионы леменгов хотят принести ООП и систему классов

            Да, это целая проблема. Потому что: https://www.youtube.com/watch?v=wfMtDGfHWpA
            Ещё примеры: https://medium.com/javascript-scene/how-to-fix-the-es6-class-keyword-2d42bb3f4caf а так же многие другие статьи Eric Elliott
            Один из вариантов решения (возможно не самый лучший, но не плохой так точно): https://github.com/stampit-org/stampit


            jQuery чисто прототипная библиотека, хоть new и скрыт за фабричной функцией.
            Backbone, верно, проблема разобрана здесь: https://vimeo.com/69255635
            Lodash, с точки знения пользователя вообще функциональная библиотека.


            Вот представим, что у нас есть Quake, написанный на js (Quake — это очень маленькое, по объемам логики, приложение). И у нас есть ошибка, конкретную точку, где она проявляется — мы знаем. Как найти все пути, которые приводят к этой точке кода?

            Возможно FP может помочь? Например Redux с глобальным state и Immutable data может помочь отследить что привело к этой ошибке. Плюс в реальном времени её можно пофиксить. Поскольку весь код написан на pure functions, никаких посторонних эффектов быть не должно.


            1. AndreyRubankov
              22.08.2016 00:21
              +1

              >> Но! Почему-то миллионы леменгов хотят принести ООП и систему классов
              > Да, это целая проблема. Потому что: https://www.youtube.com/watch?v=wfMtDGfHWpA

              Система классов не подразумевает под собою наследование. Более того, с наследование — это действительно боль (в большинстве случаев). Классы очень хорошо можно использовать для типизации (классификации), к примеру, чтобы не в комментах писать, какой тип может приниматься методом, а напрямую в декларации метода его вставить. Так же, система классов позволила бы улучшить производительность рантайма: точно известно сколько памяти нужно выделить, точно известно, сколько полей есть в классе, известны ссылки на методы и т.д.

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


              1. sumanai
                22.08.2016 00:44

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

                Для этого разве нужны классы?


                1. AndreyRubankov
                  22.08.2016 01:29

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

                  Сразу оговорюсь, мне больше нравится немного улучшенный Си-шный вариант ООП: структуры с методами, которые имплементят интерфейсы.
                  Интерфейсы описывают Тип, «структуры с методами» (так и тянет сказать Класс) — имплементация Типа. Желательно без наследования.
                  Все взаимодействие ведется либо через Тип, либо через Конкретные «структуры с методами» (когда разные имплементации не подразумеваются).
                  — это я подразумеваю под определением Класса.

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


              1. zorro1211
                22.08.2016 01:34

                Спасибо за ответ!


                будет очень больно

                Да, та же проблема что и у multiple inheritance. Хорошо что в js её не так сложно решить как в java, c#.


            1. AndreyRubankov
              22.08.2016 01:06

              > Возможно FP может помочь? Например Redux с глобальным state и Immutable data
              > Поскольку весь код написан на pure functions, никаких посторонних эффектов быть не должно.

              1. FP, Immutable data
              Не столь принципиально, каким путем пойти, FP и OOP справятся в целом одинаково.
              Но вот Immutable data… Immutable data для шутера! В этом случае GC будет вызываться очень часто, ваш ноутбук будет перегреваться, FPS будет… FPS иногда будет =)

              2. pure function, global state
              если все будет на pure function, то кто будет обновлять состояние?))


              1. zorro1211
                22.08.2016 01:29

                1. Признаюсь тут я дилетант, но даже мои дилетантские тесты показывают что для большого количества объектов постоянное клонирование приводит к задержкам, так что я согласен. Интересно, есть ли материалы по решению этой проблемы для ФП...
                2. :) Не до такой степени pure конечно. Примерно как в Redux.


                1. AndreyRubankov
                  22.08.2016 01:38
                  +1

                  Из того, что встречалось мне, в качестве решения можно использовать билдер, но в приделах одной функции.
                  Если нужно выполнить 10 изменений, можно из объекта сделать билдер, на нем произвести эти 10 изменений, и наружу отдать новый экземпляр неизменяемого объекта. Тем самым вместо 10 клонирований будет всего 2.

                  но это отступничество от идеологии, как и global state =)


            1. TheShock
              22.08.2016 04:22

              Возможно FP может помочь? Например Redux с глобальным state и Immutable data может помочь отследить что привело к этой ошибке. Плюс в реальном времени её можно пофиксить. Поскольку весь код написан на pure functions, никаких посторонних эффектов быть не должно.

              Вы пробовали дебажить Редакс? Крупные игры — это не веб-сайты, где много одинаковых табличек, тут кустарные вещи работать не будут.


              1. zorro1211
                22.08.2016 06:50

                Уважаемый TheShock вам по-любому виднее! Я только разбираюсь c ФП и считаю что хорошо понимать где его нужно использовать, где не нужно. Тоже самое и с class, closure, и т.п. Особенно в свете последих статей от Eric Elliot, а также материалов от Brian Lonsdorf. Хотелось бы материалов без предпочтения сравнивающих разные подходы, рассматривая где и когда их использовать. Но раз такого нету, приходится через комментарии (ну и в личных проектах) вот так узнавать мнение =) Простите мне мое лукавство.


                П.с. спасибо вам за ваши статьи и комментарии на хабре, всегда очень интересно вас читать!


    1. AjnaGame
      20.08.2016 20:10
      +1

      Да, после ас3, код на джс выглядит уж больно кастурбатым. Остаётся haxe.


    1. SerafimArts
      21.08.2016 01:09
      +3

      Flowtype для слабаков, да? ;)


      1. AndreyRubankov
        22.08.2016 13:20
        +1

        Да, я в курсе про Flowtype, но заметьте — это сторонний плагин для babel, чтобы расширить стандарт ES. Это даже не proposal для ES7.

        Вот прям из всех щелей прет типизация, но ее не пускают и даже не хотят рассматривать. А прет на сколько сильно, что даже пишут свои велосипеды (хинты через jsdoc) и расширения для стандарта как плагины для babel.

        Мое сообщеним именно про это и было, что развитие похоже на изобретение нового лучшего велосипеда. И потом годами идет разнобой в спецификации и в реализации. По спеке уже давно есть классы, генераторы и т.д., на деле — только babel спасает, нужно все в ES5 транслировать.

        Можно конечно сказать «Да кому нужны пользователи IE!!! Пусть ставят FF или Chrome!», но как ни крути, не везде такой ответ будет адекватным. Да ни FF, ни Chrome не поддерживают еще полноценно ES2015, а уже почти 1 год прошел :)


        1. SerafimArts
          22.08.2016 19:40

          Я не столь часто использовал TypeScript, так что могу ошибаться с выводами, но на моём опыте, используя его — там сделаешь поблажку, тут, у тебя всё в конце концов, грубо говоря, превратится в один большой any, хотя казалось бы, всё так хорошо начиналось. И требования к декларации d.ts всё только усугубляют, хотя куча сторонних уже готовых деклараций спасают положение, но всё же...


          Мне нравится Flow именно потому, что типизация опциональна и нужна "только там, где нужна". На всё "фигак-фигак" можно смело забивать болт и писать на чистом ES5, а любой критичный код можно собирать даже с рантайм проверками (по крайней мере в dev'е). Единственное чего не хватает — это, например экспортирования указанных типов в Metadata, например для последующего анализа внутри DI контейнера для двойной диспатчеризации. А так — идеал, никому не мешает — хочешь используй, хочешь — нет.


          Ну это моё имхо.


          1. AndreyRubankov
            22.08.2016 20:27

            После знакомства с TypeScript я начал сомневаться в адекватности его авторов. Они взяли js добавили к нему типизацию, но сделали это криво. Сделали это через велосипед и костыли. А ведь все уже было готово, готовый язык, готовые ИДЕ, статические анализаторы и прочее. Нужно было лишь взять и дописать транслятор AS3 -> JS. Заменить flash sdk, на web sdk или node sdk — и вуаля, у тебя мощный инструмент для созданий серьезных веб-приложений. Если бы они так сделали, глядишь, сейчас не изобретали бы еше одно колесо, а прочно засели бы на серверах и дали бы буст для производительности в браузерах.


            1. SerafimArts
              22.08.2016 20:49

              Полностью согласен, но это всё издержки подхода "без строгой типизации везде и всюду — жизнь не мила".


              P.S. При таких запросах… Ну так Haxe же: https://haxe.org/ =)


            1. staticlab
              22.08.2016 20:59

              Чтоб Майкрософт — и не придумывала свой велосипед?


            1. Kalifriki
              22.08.2016 21:03

              Они взяли js добавили к нему типизацию, но сделали это криво

              А можно поподробнее?


              1. SerafimArts
                22.08.2016 21:53
                -1

                Ну это надо либо самому прочувствовать, либо почитать о d.ts хотя бы: https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html (TL;DR: Костыль, ради идеологии)


                1. arvitaly
                  22.08.2016 22:32
                  +1

                  Для чего вам еще один язык, компилирующийся в JS https://github.com/jashkenas/coffeescript/wiki/list-of-languages-that-compile-to-js?
                  Вопрос стоял очень просто, добавить немного статического анализа хотя бы на уровне своего приложения. Причем без overhead в виде килобайтов и длительной загрузки собственной системы типов на клиентской стороне.
                  Никто не рвется переписывать тысячи JS-библиотек, так как типизации нет на уровне виртуальной машины в браузере, и ни один из существующих компиляторов не способен выдать из другого языка высокоэффективный JS-код.
                  А работать надо здесь и сейчас. И использовать чужие библиотеки, написанные не на typescript, зато оттестированные и проверенные тысячами разработчиков. Отсюда необходимость в d.ts, ну либо как во flow, каждый сам проверяет интерфейсы чужих библиотек.
                  Резюмируя, TypeScript никогда не позиционировался, как «убийца JS», мало того, не зная JS, писать на нем — самоубийство. Это «синтаксический сахар для JavaScript» https://msdn.microsoft.com/ru-ru/magazine/dn890374.aspx.
                  И это эффективный помощник для тех, кто понимает.


                  1. alex_blank
                    22.08.2016 23:24
                    -1

                    так как типизации нет на уровне виртуальной машины в браузере
                    не способен выдать из другого языка высокоэффективный JS-код

                    На самом деле есть: https://en.wikipedia.org/wiki/Asm.js. То есть типизация на уровне VM вполне себе есть, просто она неявная. Можно писать такой код, который будет в очень эффективные конструкции JIT'ом скомпилирован. Вот из вики пример:


                    int f(int i) {
                      return i + 1;
                    }

                    function f(i) {
                      i = i|0;
                      return (i + 1)|0;
                    }

                    Вот эти |0 гарантируют компилятору, что результатом выражения будет тип integer, и что это можно в эффективные инструкции скомпилировать. Ну это самый примитивный пример. Компилятор Emscripten автоматически все это генерит (это транспилятор из LLVM байткода в JS)


                    На деле JIT очень продвинутые оптимизации делает, например может понять где у вас POD-структура, а где динамический словарь.


                    1. alex_blank
                      22.08.2016 23:29

                      (соответственно, если бы у нас в языке были явные хинты типов, то это существенно облегчило бы работу JIT'у; но суть в том что «под капотом» JS все очень даже типизировано уже — осталось лишь во «фронт-енде» — в самом языке — хинтинг типов добавить — и можно неиллюзорный профит ловить, от возросшей производительности)


    1. babylon
      22.08.2016 19:00

      Любой язык построенный на классах, и AS3 в этом смысле не исключение, рано или поздно приводит к росту статического ядра. Выходом из данной ситуации могла бы послужить введенная в ES6 конструкция Object.setPrototypeOf(child, parent), позволяющая динамические строить иерархии используя классы как объекты; Но в существующих фреймворках данный подход почему-то не просматривается.


      1. AndreyRubankov
        22.08.2016 20:13

        Если честно, не совсем понимаю, что вы имеете ввиду под «ростом систического ядра», и что за статическое ядро. Не могли бы вы рассказать про это? И какая в этом проблема?

        На счет Object.setPrototypeOf, не совсем понятно, каким образом прототипное наследование «извне» будет лучше классического (или того же прототипного, но с классическим привкусом, как в ES2015) наследования «изнутри».

        Динамически собирать иерархию объектов, с возможностью «множественного наследования», когда порядок подключения родителей имеет значение и порою бывает критическим. Думаю, это не лучший способ построения кодовой базы. Пока это небольшой проект — все будет довольно не плохо, а когда в системе будет несколько сотен сущностей — тут уже будет тяжело поддерживать и разбираться.

        Возможно, я не прав или не понимаю этой концепции, но на данный момент у меня такая точка зрения.


        1. babylon
          22.08.2016 22:16

          Андрей, есть два способа организации кода — наследование и композиция. Композиция предполагает применение чужеродного кода. Но если этот код полезен, то рано или поздно он переносится в ядро. Возьми Drupal например. Не вижу ничего плохого в «множественном наследовании». У вас в есть указатели на классы. В запросе указываешь на тот который нужен. Именно иерархия будет динамически собираться. Если надо то и сохранятся в виде снимка иерархии из указателей и данных. Порядок подключения определяется следованием элементов в массиве. В маппинге задать последовательность в принципе невозможно. Запустил, поднял из репозитария иерархию, поработал. Если надо сохранил. Все дела.


  1. Miraage
    20.08.2016 20:27

    Начнем с того, что компоненты React — функции. Ничего никуда не делось.


    1. alex_blank
      20.08.2016 22:28

      Ну да, это просто терминологическая путаница. Я почти сразу подправил статью после публикации, просто видимо вам в RSS (или еще куда) старая версия подсосалась.


  1. OlegLustenko
    20.08.2016 21:13
    -1

    А как на счет

    class User {
    get hi(){
    return 'hi'
    }
    }

    let Vova = new User();
    console.log (Viva.hi)

    Мне этого в моей реализации было достаточно.

    Тоже изучал тему статических
    свойств классов, на 2ality есть ссылка, что это антипатерн.

    Еще есть бабелевская обертка для использования статических свойств.


    1. stas404
      20.08.2016 23:38
      +1

      У вас «Вова с приветом» привет передавал, но устал и не добрался до консоли.


  1. serf
    20.08.2016 23:17
    +2

    «Крутые чуваки» просто лишний раз хотят засветить свое имя говоря что классы не нужны. Вся эта функциональщина должна быть помещена в голову для анализа полной структыры проекта, желаю удачи этим чувакам с их миксинами при работе с большимим поектами. В конце концов не нужные классы не используй, но не нужно называть свою функциональщину серебрянной пулей. Типов в JS не ожидается, потому что предоплагаю странные люди пишу стандарты. Вот TypeScript сделали инженры, а не хипстеры.


    1. zorro1211
      21.08.2016 23:53

      У вас очень односторонний комментарий :)
      https://youtu.be/n6MRsGwyMuQ?t=4m54s


      Нужно понимать чем классы могут навредить: https://www.youtube.com/watch?v=wfMtDGfHWpA, http://martinfowler.com/bliki/CallSuper.html
      И как с этим бороться: https://github.com/stampit-org/stampit, миксины


      1. serf
        21.08.2016 23:59

        К чему мне ваши ссылки на ресурсы для джуниоров? Я ваши миксины использовал более 10 лет назад, когда их так еще не называли. Так и не поняли чем эти миксины могут навредить большому проекту? Хипстерня любит функциональщину, это очевидно, но на практике такой подход не всегда оправдан.


        1. zorro1211
          22.08.2016 00:05

          К чему мне ваши ссылки на ресурсы для джуниоров?

          Вы забыли пару восклицательных знаков =)


          Так и не поняли чем эти миксины могут навредить большому проекту?

          Хотелось бы разобраться. Если вас не затруднит.


          1. serf
            22.08.2016 00:07

            Хотелось бы разобраться. Если вас не затруднит.

            Попробуйте порефакторить эту «функциональную кашу», сразу разберетесь. Функциональщина делает из кода месиво, которое не может быть однозначно интерпретировано на этапе разработке. Происходит разработка скриптов.


            1. zorro1211
              22.08.2016 01:12

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


              1. serf
                22.08.2016 12:14

                Я не пытаюсь даже спорить, мне лично понятны недостатки и преимущества обоих подходов. Эта чудесная композиция / миксины и ФП (все это делает из кода неконтролируемое месиво) в целом подходят только для простых проектов, можно сказать для проектов где работает один человек которому скучно писть поддерживаемый код (назовем его хипстер).


  1. duskpoet
    21.08.2016 00:08
    +1

    Странно, насколько я помню, пока что в React не особо успешно решена проблема пересчета виртуального DOMа у деток пересчитываемого компонента, даже если они не изменяются.
    https://jsfiddle.net/duskpoet/69z2wepo/53398/


    1. alex_blank
      21.08.2016 00:12

      Мб туда надо PureRenderMixin вкорячить?


      https://facebook.github.io/react/docs/pure-render-mixin.html


      Просто, как я понимаю, по дефолту Реакт свои компоненты не считает за чистые функции. И вызывает render() всегда. Я именно поэтому и упомянул два раза в статье, что описываю «идеализированный» Реакт :)


      1. duskpoet
        21.08.2016 00:34
        +1

        Ага, тогда вполне неплохо. А Redux похоже вообще отказались от React-классов и используют чистые функции. Хорошо люди устроились.


      1. MrCheater
        21.08.2016 22:46
        +1

        Кстати — в React 15.3.0 добавили класс верхнего уровня React.PureComponent. Чтобы нормальные пацаны от него наследовались, а не от React.Component. Там render вызывается только, когда нужно


  1. SbWereWolf
    21.08.2016 00:46
    +1

    познавательно, спасибо.


  1. SerafimArts
    21.08.2016 01:08

    С добрым утром, декораторы депрекейтнули и уже года полтора думают что с ними делать. Т.е. по факту в языке и компиляторе (babel 6) их нет, только в виде leagcy пакета. =)


    1. alex_blank
      21.08.2016 01:13

      Ну это меня не печалит, потому что у меня свои «декораторы», которые вообще даже в ES5 работают. Я просто прикидывал, как их можно было бы реализовать, будь у нас нативный синтаксис. Хотя по мне, лучше бы они классы депрекейтнули, ха-ха.


    1. Mingun
      21.08.2016 08:30
      +1

      Офигеть, ещё даже не зарелизили, а уже депрекейтнули… Куда катится этот язык?


      1. alex_blank
        21.08.2016 14:24
        +1

        Это просто SerafimArts панику наводит. На самом деле это не депрекейт, а просто обновляют черновик стандарта, и пока апдейт идет, из Babel временно убрали текущую реализацию (она доступна в виде внешнего плагина). Это нормальный процесс работы, и декораторы еще вернутся. Их уже юзают многие технологии популярные — например ангуляр2, mobx.


        1. SerafimArts
          21.08.2016 14:37
          +1

          Ну это с иронией было сказано. Никуда они не делись, но там ситуация примерно как с модулями в плюсах: Собираются, ставят на повестку вопрос "ну что будем делать с декораторами?", отвечают — "ничего". На этом обсуждение заканчивается. +)


  1. PaulMaly
    21.08.2016 02:52
    +4

    Третий год использую RactiveJS и пока не разочаровался. Для любителей ReactJS посоветовал бы еще глянуть на InfernoJS. Говорят у них diff сделан значительно лучше.


    1. Elfet
      22.08.2016 17:45

      Та часть что про keyed lists в диффе.


      1. PaulMaly
        22.08.2016 17:55

        Честно говоря подробно не разбирался. Мне вот RactiveJS нравится почти всем, его и использую, хотя знаю что тесты по скорости он проигрывает фактически все. Пока меня это не беспокоило, видимо задач не было где бы я увидел разницу между тем как рендерит ReactJS и RactiveJS. Другим может и не подойти конечно.


  1. pawlo16
    21.08.2016 11:39
    +2

    Будем честны. Все поднятые Вами проблемы решаются сменой языка программирования на тот, в котором statless и promises реализованы на уровне языка. Elm, Clj, F# и ни разу не на TypeScript или "статический" js + Flow. Например


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

    Но изначально React создавался, как урезанный Elm с классами. Ну и тэ пэ


    1. alex_blank
      21.08.2016 11:41
      +1

      Я в соседнем топике как раз про это и пишу:

      Я кстати уверен что в будущем люди не то что «забудут этот недохаскель». Но реально перейдут на хаскель. Ну точнее его браузерную инкарнацию какую-нибудь, типа Elm. Очевидно же, что все к тому идет, просто не революционным, а эволюционным путем. JS все больше и больше становится похож на это, в какой-то момент уже просто не будет смысла использовать JS, все свитчнутся на какой-нибудь транспилируемый в него язык. Они и сейчас есть, такие языки, просто люди еще не понимают до конца, зачем они нужны — но это в какой-то момент станет очевидно всем. И появление таких вещей как React/Redux в массовом сознании это очень мощные сигналы к этому.

      [...]

      И тут интересный дальше момент — это определяет архитектуру нашего приложения. То есть, выгодно юзать Redux. А это уже все прямиком пришло их функциональных языков, из Elm в частности. И постепенно никакого смысла в том чтобы использовать JS вместо Elm не будет. Потому что Elm позволяет еще и статически верифицировать всю программу так, что у вас вообще нет runtime exceptions. А это и есть ультимативная цель всего этого замеса.


      1. hellosandrik
        21.08.2016 14:14
        +1

        Ну, у Elm судя по обсуждениям на hackernews есть свои проблемы, в частности запутанный ffi (вызов js из elm). К тому же это довольно упрощенный Haskell ориентированный на фронтенд, т.е. не язык общего назначения. Мне самому очень нравится Haskell и поэтому это меня немного огорчает… Смотрю в сторону ghcjs и purescript. Что о них думаете?


        1. alex_blank
          21.08.2016 14:15

          Я хаскеллом не обмазываюсь уже лет восемь, поэтому совершенно выпал из контекста — про ghcjs и purescript впервые слышу. Я про Elm-то узнал буквально на днях, когда узнавал откуда у React/Redux ноги растут. Даже не успел его попробовать толком.


        1. pawlo16
          21.08.2016 14:40

          упрощенный Haskell ориентированный на фронтенд

          Не упрощённый Haskell общего назначения избыточен и слишком сложен для решения проблем, типичных для клиентской вебморды.

          запутанный ffi (вызов js из elm)

          Иначе Elm не даст гарантий безопасности рантайма, очевидно же.


        1. easimonenko
          21.08.2016 15:15
          +1

          Пробую писать на Elm. FFI как такового там нет. Есть так называемые port для взаимодействия с кодом на JavaScript. И есть native-модули, которые естественно пишутся на JavaScript. Непосредственного вызова JavaScript из Elm нет.


          Любители Haskell могут также посмотреть на Fay. В отличие от Elm это упрощённый Haskell (нет классов типов) с тривиальным FFI.


      1. pawlo16
        21.08.2016 14:26
        +1

        Вряд ли Haskell такой уж идеальный ЯП что вот так прям взлетит в будущем, учитывая его сложность. Упомянутый Elm скорее взлетит, ибо он проще. Я собственно про то, что его можно же уже сейчас брать. То есть те причины, по которым его люди избегают ("у меня в проекте DataTimePicker из jQuery UI, как мне его вставить в Elm ?!") мне не кажутся убедительными, если только нет действительно большого количества legacy кода.


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


      1. arvitaly
        21.08.2016 17:29

        Я не согласен про JavaScript, как язык, он очень универсален и прост. Поэтому интерпретаторы JS есть везде, их относительно легко писать. Попробуйте написать для haskell кучу GHC для всего на свете, это адский труд, который должен быть оправдан (например, повышенная надежность мед. оборудования и т.д.).
        Другое дело, что многие web-задачи стали именно такими, требующими скорость исполнения, максимальное покрытие тестами (требованиями) и т.д. И в JS просто нет многих вещей для этого. Но, ИМХО, и не нужно. Ведь задачи, для которых он был создан никуда не делись.
        Ну ок, неудобно на нем писать сами компоненты, зато теперь есть пока еще не такая крупная и сложная задача — компоновка этих компонентов, сборка и доставка (webpack.config.js, gulp и т.д.). Станет эта задача большой и сложной, напишем под нее компилятор haskell.
        Зато появится задача управления docker-контейнерами, когда нам станет не хватать docker-compose.yml с его возможностями.
        Эта цикличность никуда не денется: начинается все с конфигов, потом сложных конфигов с переменными, потом простых скриптов, потом сложных скриптов, а потом уже (через N шагов) вся мощь haskell :-) Я кстати вот не уверен, что пора уже прям до haskell доводить, может хватит пока Java, C# уровня? Так рассудили и создатели TypeScript.

        Посмотрите на стили, та же история, CSS, SCSS и сейчас уже в react вот и стили программируемые (react material ui), а в react native и вовсе один способ.
        В общем, не нужно трогать JS, нужно перетерпеть ситуацию с компилированием в него и дождаться webassembly.

        Есть еще такой момент для размышлений. Что, если нам вообще не нужна надежность и гарантия в браузере? Что если пользователь является частью экосистемы и сам допиливает то, что ему нужно (привет, adblock, userscript и др расширения). Можно конечно давать менять какие-то жестко зашитые вещи (ну вроде как поддержка плагинов), но что если гибкость до самого корня (DOM, сеть и т.д.) является залогом успеха развития веб? Для чего мне жестко зашивать и проверять кучу параметров, а потом выслушивать, что исчез логотип хабра, если каждый пользователь все равно создаст собственный хабр в браузере для просмотра статей. Может стоит не бороться с этим, а возглавить?

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


        1. staticlab
          21.08.2016 21:02

          Извините, а что всё-таки решит webassembly? Не придётся верстать, писать стили и управлять DOM? Сами же говорите, что это не так уж и плохо.


          1. zorro1211
            22.08.2016 00:01

            Здесь приведены примеры проблем которые решит webassembly: https://medium.com/javascript-scene/what-is-webassembly-the-dawn-of-a-new-era-61256ec5a8f6#.9to9ao9py


            1. staticlab
              22.08.2016 10:00

              Я вижу только, что всё сводится к возможности использования альтернативных языков и общему повышению производительности из-за прекомпиляции. Но ведь это ничего принципиально не меняет. Уже существует множество альтернативных языков для веб-платформы, включая статически типизированные (CoffeeScript, TypeScript, ClosureScript, LiveScript, Elm, Dart и т.д.), некоторые из них даже предлагают свою стандартную библиотеку (Dart, Elm, ClosureScript и т.д.). То есть разница в данном случае будет только в генерируемом коде (JavaScript или WebAssembly). Далее, всё равно остаётся проблема построения интерфейса. Либо придётся верстать вручную (как "классически", так и через некие DOM-обёртки типа React, может даже это будет выглядеть по типу WPF, JavaFX, QML или Android Layout XML), либо будут некие компонентные фреймворки вроде ExtJS, Dojo и т.п. То есть, если честно, я ничего принципиально нового пока не вижу, разве что потенциальное "утяжеление" фреймворков, компенсируемое повышением производительности виртуальной машины.


        1. pawlo16
          22.08.2016 00:52
          +1

          зато теперь есть пока еще не такая крупная и сложная задача — компоновка этих компонентов, сборка и доставка (webpack.config.js, gulp и т.д.)
          В нормальных платформах/языках это делает компилятор/линковщик. В js это, как и всё остальное, реализовано через одно место — с помощью гимнастики на волшебных костылях.

          Что, если нам вообще не нужна надежность и гарантия в браузере?

          Простите, но это похоже на бессвязный поток понятий


  1. G-M-A-X
    21.08.2016 13:18
    -3

    Как по мне, фейсбук (автор Реакта) не тот сайт, для которого нужно было городить огород.
    Там хватило бы и jQuery.
    Код должен быть понятен большинству, а не только крутым перцам.
    Такой код дороже.
    А еще фейсбук память жрет.


    1. alex_blank
      21.08.2016 13:49
      +3

      На самом деле, код на jQuery намного дороже. Это примерно как лечить зубы у плохого стоматолога. Вроде бы дешевле, но выходит что дороже. И потерянное время вам никто не вернет.


      И оправдано это именно в мегапроектах типа Фейсбука. У них тысячи компонентов, миллионы строк кода. Для маленьких же сайтов-визиток, конечно же, лучше использовать jQuery. Или вообще голый DOM (в jQuery смысла не очень много с появлением нативных селекторов).


      1. G-M-A-X
        22.08.2016 10:04

        Я не спорю, для многих сайтов (скорее приложений с веб-интерфейсом) больше может подходить какой-то фреймворк.

        Но вот как раз фейсбук не такой сайт.

        Вот почему у них миллионы строк кода?
        Потому что, грубо говоря, не используют jQuery, а городят огороды.

        https://habrahabr.ru/post/308148/

        Вряд ли стоит писать на голом js.


        1. alex_blank
          22.08.2016 10:14

          Вы уже много фейсбуков написали? :) Просто у вас о нем очень примитивизированное представление. Любой веб-проект на деле намного сложнее чем кажется.


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


          1. staticlab
            22.08.2016 10:28

            Фейсбук стали использовать React не так давно, значительная часть их кода всё еще на условных «джиквери»

            Что не помешало авторам множества статей о Реакте навести хайп во всём Интернете ещё пару лет назад: "Гугл сам свой Ангуляр нигде не использует, а Фейсбук уже весь такой модный на Реакте".


            1. alex_blank
              22.08.2016 10:29

              Есть такой принцип — «не сломано, не трогай». Никто не будет переписывать миллионы строк работающего кода, только чтобы чуваки могли еще парочку статей об этом написать в модный журнал.


              1. staticlab
                22.08.2016 10:43

                Естественно, в крайнем случае это будет медленный процесс переписывания старого кода.


          1. G-M-A-X
            22.08.2016 13:19

            >Любой веб-проект на деле намного сложнее чем кажется.

            Не спорю.
            С морды всей кухни не увидеть.
            Но js это более морда и не понимаю, чего там у ФБ такого сложного.

            ЛС
            Подгрузка ленты
            Комменты

            Там больше сложностей на бекэнде, а не на фронте.


    1. serf
      22.08.2016 00:16
      +1

      Код который обычно пишут на jQuery абсолютно не поддерживаемый и не развиваемый. Задача jQuery очень узкая. В первую очередь при разработке чего-то большого необходима структура и модульность. jQuery структуру не предоставляет, и большая часть любителей jQuery не способна самостоятельно задать гибкую структуру.


      1. G-M-A-X
        22.08.2016 10:28

        >В первую очередь при разработке чего-то большого необходима структура и модульность.

        Это так сложно разбить код хотя бы на файлики?

        >jQuery структуру не предоставляет

        А плагины?

        >и большая часть любителей jQuery не способна самостоятельно задать гибкую структуру.

        То есть Вы не используете jQuery из-за того, что кто-то не может задать гибкую структуру? :)


        1. staticlab
          22.08.2016 10:58
          +1

          jQuery структуру не предоставляет

          А плагины?

          Вы хоть раз видели, как кто-либо пишет код проекта плагинами? В том и дело, что "большая часть любителей jQuery" этим даже не заморачивается. А если в проекте собрались "крутые перцы", которые могут написать "по уму", то почему бы им не взять более удобную технологию? И фейсбук, всё-таки, солидная богатая компания, которая может себе позволить нанять на последующую поддержку кода опытных специалистов, а не зелёных новичков.


          1. G-M-A-X
            22.08.2016 13:01

            >Вы хоть раз видели, как кто-либо пишет код проекта плагинами?

            Нет.
            Сам использую их только для повторного использования.
            Но ничто не мешает.

            >В том и дело, что «большая часть любителей jQuery» этим даже не заморачивается.

            Ну да, это большинству просто не нужно. :) Вы тоже не используете jQuery из-за того, что кто-то что-то не умеет? :)

            >А если в проекте собрались «крутые перцы», которые могут написать «по уму», то почему бы им не взять более удобную технологию?

            Выходит менее понятно :)
            Куча дырявых абстракций.
            https://habrahabr.ru/post/308154/#comment_9763712

            Удобную, но более сложную, технологию стоит использовать, когда это оправдано.
            Для сайта вроде ФБ и проще вряд ли это оправдано.

            Кому-то нравиться прыгать с технологии на технологию, а кто-то решает задачи.


            1. staticlab
              22.08.2016 13:55
              +1

              Любая незнакомая технология может выглядеть непонятной. Кроме того, проект на чистом jQuery тоже не будет лишён дырявых абстракций. Кроме того, "крутые перцы" так или иначе или вокруг jQuery напишут свой недофреймворк (типа Backbone или чего-то подобного), или код рано или поздно станет макаронным и неподдерживаемым.


              Для сайта вроде ФБ и проще вряд ли это оправдано.

              "И проще" включает всё вплоть до самых простых одностраничников?


              Кому-то нравиться прыгать с технологии на технологию, а кто-то решает задачи.

              В крупных проектах так и делают, но спустя несколько лет при необходимости задумываются о рефакторинге или переходе на что-то другое. Об этом судят, исходя из реалий проекта.


        1. serf
          22.08.2016 12:16

          А что плагины? Плагины всего лишь расширяют функциональность работы с DOM.


          1. G-M-A-X
            22.08.2016 12:36

            Вообще-то плагины не только ДОМ.


  1. G-M-A-X
    22.08.2016 10:15

    Хабр не туда коммент вставил :)