Болезни Военный билет Призыв

Строим свой full-stack на JavaScript: Основы. Параллельное выполнение кода и цикл событий

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

По моему опыту (под которым далее будем понимать полный цикл разработки на JavaScript) выполняет все вышеперечисленные задачи. Возможно, Вы где-то с ним сталкивались; вероятно, Вы убеждены в его эффективности и даже спорили с друзьями по этому поводу. А сами пробовали? В этом посте я сделаю обзор full-stack JavaScript, расскажу зачем он Вам нужен и как все это работает.

Почему я использую JavaScript

Я веб-разработчик с 1998 года. В те времена чаще всего мы использовали Perl для серверной разработки, но уже тогда на стороне клиента был JavaScript . С тех пор серверные технологии сильно изменились: одна за другой нас накрывали волны языков и технологий, таких как PHP, ASP, JSP, .NET, Ruby, Python и др. Разработчики стали осознавать, что использование двух разных языков на клиенте и сервере всё усложняет.

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

alert("Welcome");

Или еще хуже:

var users_deleted = ; users_deleted.push("");

У начинающих существовали типичные проблемы с пониманием операторов в разных языках, к примеру таких, как for или foreach . Более того, написание подобного кода на сервере и на клиенте для обработки одних и тех же данных – неудобно даже сегодня:

$.ajax({ url:"/json.php", success: function(data){ var x; for(x in data){ alert("fruit:" + x + " points:" + data[x]); } } });

Первые попытки делать всё на одном языке заключались в создании клиентских компонентов на сервере и компиляции их в JavaScript. Это не работало так, как ожидалось, и многие их тех проектов провалились (к примеру, ASP MVC, заменивший ASP.NET Web forms , и GWT , которому на смену пришел весьма сомнительный Polymer). Но сама по себе идея была замечательной, а особенно: единый язык на клиенте и сервере, позволяющий нам повторно использовать компоненты и средства (ключевое слово здесь: средства).

Решение было простым: запустить JavaScript на сервере.

Одностраничные приложения

При работе с full-stack JavaScript часто приходится создавать одностраничные приложения (SPA). Многие веб-разработчики ни раз искушались попробовать себя в SPA. Вы когда-нибудь сравнивали SPA и обычное веб-приложение при мобильном соединении? Разница в отклике порядка десятков секунд.

(Примечание: некоторые могут с этим не согласиться. Twitter, например,

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

В первом приближении массивы - это привычная и популярная структура данных. Но работа с ними как со стеком придает им не предусмотренные синтаксисом языка возможности. Добавление/удаление посредством JavaScript push/pop в конец или unshift/shift в начало не только удобно, но и практично.

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

Массив можно пополнять новыми элементами при помощи метода push. Результатом этого метода будет новое количество элементов массива. Обратная процедура - метод pop не имеет параметров, но выдает в качестве результата последний элемент массива.

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

JavaScript push object - нонсенс или прогресс?

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

Вообще говоря, то, что есть в JavaScript, до сих пор не позволил себе иметь ни один «свободный» от браузера язык программирования. Самое оригинальное - создание объекта здесь - дело рук программиста, начиная с имени объекта.

Методы JavaScript pop&push при использовании объектов предоставляют программисту возможность создать многофункциональный объект в прямом значении этого слова.

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

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

Стек, массив и организация данных

Существует много задач, когда результат требует многовариантного выбора. Если для реализации выбрать комплект операторов if или case, получится большой, длинный и ветвистый «куст» условий.

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

При помощи стека практически во всех случаях можно поступить проще.

Есть задача: нужно выбрать исполнителя из сотни доступных. Каждый исполнитель может делать что-то из трех позиций (от одной до трех в любом сочетании):

  • t - делать техническое обслуживание;
  • s - может полностью выполнять ремонтные работы;
  • i - имеет право делать гарантийный ремонт.

Чтобы быстро выбрать исполнителя для заказа с нужным видом (видами работ), можно сделать три операции JavaScript push и слить массив в одну строку.

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

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

Стек

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

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

В качестве более технологичного примера стека можно привести операцию «Отменить » (Undo ) в текстовом редакторе. Каждый раз, когда текст вводится в текстовый редактор, он помещается в стек. Самое первое изменение текста представляет собой дно стека; самое последнее — вершину. Если пользователь хочет отменить самое последнее изменение, удаляется верх стека. Этот процесс может повторяться до тех пор, пока не останется ни одного изменения.

Операции стека

Так как теперь у нас есть концептуальная модель стека, давайте определим две операции стека:

  • push(data) — добавляет данные;
  • pop() — удаляет последние добавленные данные.
Реализация стека

Теперь давайте напишем код для стека.

Свойства стека

Мы создадим конструктор с именем Stack . Каждый экземпляр Stack будет иметь два свойства: _size и _storage :

function Stack() { this._size = 0; this._storage = {}; }

this._storage — позволяет каждому экземпляру Stack иметь собственный контейнер для хранения данных; this._size отображает сколько раз данные были добавлены в текущую версию Stack . Если создается новый экземпляр Stack и в хранилище добавляются данные, то this._size увеличится до 1. Если данные снова добавляются в стек, this._size увеличится до 2. Если данные удаляются из стека, this._size уменьшается до 1.

Методы стека

Определим методы, с помощью которых можно добавлять (push ) и удалять (pop ) данные из стека. Начнем с добавления данных.

Метод push(data)

Этот метод может быть общим для всех экземпляров Stack , так что мы добавим его в прототип Stack .

Требования к этому методу:

  • Каждый раз, когда мы добавляем данные, размер стека должен увеличиваться;
  • Каждый раз, когда мы добавляем данные, порядок стека должен сохранять свою последовательность:
  • Stack.prototype.push = function(data) { // увеличение размера хранилища var size = this._size++; // назначает размер в качестве ключа хранилища // назначает данные в качестве значения этого ключа this._storage = data; };

    Объявляем переменную size и присваиваем ей значение this._size ++ . Устанавливаем переменную size в качестве ключа this._storage , data — в качестве значения соответствующего ключа.

    Если стек вызывал push(data) пять раз, то размер стека будет 5. Первое добавление данных в стек назначит этим данным ключ 1 в this._storage . Пятый вызов push(data) присвоит данным ключ 5 в this._storage . Мы только что задали порядок для наших данных.

    Метод 2 из 2: pop()

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

    Цели для этого метода:

  • Использовать текущий размер стека, чтобы получить последние добавленные элементы;
  • Удалить последние добавленные элементы;
  • Уменьшить _this._size на один;
  • Вернуть последние удаленные данные.
  • Stack.prototype.pop = function() { var size = this._size, deletedData; deletedData = this._storage; delete this._storage; this.size--; return deletedData; };

    pop() выполняет все перечисленные нами задачи. Мы объявляем две переменные: size инициализируется значением размера стека; deletedData назначается для последних добавленных в стек данных. Затем мы удаляем пару ключ-значение из последних добавленных элементов. После этого мы уменьшаем размер стека на 1, и возвращаем данные, которые были удалены из стека.

    Если мы протестируем текущую реализацию pop() , то увидим, что она работает в следующих случаях: если мы передаем данные в стек, то размер стека увеличивается на один; если мы удаляем данные из стека, его размер уменьшается на один.

    Но если мы выполним все операции в обратном порядке, то возникает проблема. Рассмотрим следующий сценарий: мы вызываем pop() , а затем push(data) . Размер стека становится -1, а затем 0. Но корректный размер нашего стека 1.

    Чтобы решить эту проблему, мы добавим в pop() оператор if :

    Stack.prototype.pop = function() { var size = this._size, deletedData; if (size) { deletedData = this._storage; delete this._storage; this._size--; return deletedData; } };

    После добавления оператора if , тело кода выполняется только тогда, когда в нашем хранилище есть данные.

    Полная реализация стека

    Наша реализация стека завершена. Вот окончательный вариант кода:

    function Stack() { this._size = 0; this._storage = {}; } Stack.prototype.push = function(data) { var size = ++this._size; this._storage = data; }; Stack.prototype.pop = function() { var size = this._size, deletedData; if (size) { deletedData = this._storage; delete this._storage; this._size--; return deletedData; } };

    От стека к очереди

    Стек может оказаться полезным, если мы хотим добавлять и удалять данные в последовательном порядке. Исходя из определения, из стека можно удалять только последние добавленные данные. Но что, если мы хотим удалить старые данные? Для этого нам нужно использовать структуру данных под названием очередь.

    Очередь

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

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

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

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

    Операции очереди

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

    • enqueue(data) — добавляет элементы в очередь;
    • dequeue — удаляет самые старые данные из очереди.
    Реализация очереди

    Теперь давайте напишем код для очереди.

    Свойства очереди

    Для реализации мы создадим конструктор с именем Queue . Затем мы добавим три свойства: _oldestIndex , _newestIndex и _storage :

    function Queue() { this._oldestIndex = 1; this._newestIndex = 1; this._storage = {}; }

    Методы очереди

    Теперь мы создадим три метода, распространяющиеся на все экземпляры очереди: size(), enqueue(data) и dequeue(data).

    Метод size()

    Цели этого метода:

  • Вернуть корректный размер очереди;
  • Сохранить правильный диапазон ключей для очереди.
  • Queue.prototype.size = function() { return this._newestIndex - this._oldestIndex; };

    Реализация size() может показаться вам слишком простой, но вы быстро поймете, что это не так. Давайте ненадолго вернемся к реализации метода size() для стека.

    Представим, что мы добавляем в стек пять тарелок. Размер нашего стека — пять, и каждая тарелка имеет соответствующий ей номер от 1 (первая добавленная тарелка ) до 5 (последняя добавленная тарелка ). Если мы уберем три тарелки, то у нас останется две тарелки.

    Мы можем просто вычесть три из пяти, чтобы получить правильный размер — два. Вот основной принцип для размера стека: текущий размер представляет собой корректный ключ, связанный с номером тарелки в верхней части стека (2) и другой тарелки в стеке (1). Другими словами, диапазон ключей всегда имеет границы от текущего размера до одного.

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

    Давайте представим, что первый клиент обслужен и этот талон удаляется из очереди. По аналогии со стеком мы можем получить корректный размер очереди путем вычитания 1 из 5. В очереди в настоящее время есть четыре необслуженных талона. Тут и возникает проблема: размер больше не соответствует номерам, указанным на талонах. Если мы просто вычтем 1 из 5, мы получим размер 4. Мы не можем использовать 4 для определения текущего диапазона талонов, оставшихся в очереди. В очереди остались талоны с номерами от 1 до 4 или от 2 до 5? Ответ неясен.

    Вот для чего нам нужны два свойства _oldestIndex и _newestIndex . Представьте себе, что наша система обслуживания клиентов имеет две системы выдачи талонов:

  • _newestIndex — для обслуживания клиентов;
  • _oldestIndex — для обслуживания сотрудников.
  • Когда числа в обеих системах одинаковы, это значит, что каждый клиент в очереди был обслужен, и очередь пуста. Мы будем использовать следующий сценарий, чтобы доказать эту логику:

  • Клиент берет талон. Номер талона, который извлекается из _newestIndex , равен 1. Следующий талон, доступный в системе обслуживания клиентов, имеет номер 2;
  • Сотрудник не берет билет, а текущий талон в системе обслуживания сотрудников имеет номер 1;
  • Мы берем текущий номер талона в системе обслуживания клиентов (2) и вычитаем из него номер в системе сотрудников (1), чтобы получить число 1. Число 1 представляет собой количество талонов в очереди, которые еще не были удалены;
  • Сотрудник берет талон из системы обслуживания. Этот талон представляет собой талон клиента, который должен быть обслужен. Талон, который был обслужен, извлекается из _oldestIndex , здесь отображается номер 1;
  • Мы повторяем шаг 4, и теперь разница равна нулю — в очереди больше нет талонов;
  • Теперь у нас есть свойство (_newestIndex ), которое указывает наибольшее количество (ключ ), назначенное в очереди, и свойство (_oldestIndex ), которое указывает самый первый порядковый номер (ключ ) в очереди.
  • Метод enqueue(data)

    Для enqueue у нас есть две задачи:

  • Использовать _newestIndex в качестве ключа для this._storage и использовать любые добавляемые данные в качестве значения этого ключа;
  • Увеличить значение _newestIndex на 1.
  • Мы создадим следующую реализацию enqueue(data) :

    Queue.prototype.enqueue = function(data) { this._storage = data; this._newestIndex++; };

    В первой строке мы используем this._newestIndex , чтобы создать новый ключ для this._storage и присвоить ему значение data. this._newestIndex всегда начинается с 1. Во второй строке кода, мы увеличиваем this._newestIndex на 1, и значение теперь равняется 2.

    Метод dequeue()

    Задачи для этого метода:

  • Удалить старые элементы из очереди;
  • Увеличить _oldestIndex на один:
  • Queue.prototype.dequeue = function() { var oldestIndex = this._oldestIndex, deletedData = this._storage; delete this._storage; this._oldestIndex++; return deletedData; };

    В теле dequeue() мы объявляем две переменные. Первой переменной, oldestIndex , присваивается текущее значение очереди для this._oldestIndex . Второй переменной, deletedData , присваивается значение, содержащееся в this._storage .

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

    Стек вызова функций (Call stack in programming languages)

    Как рассматривать стек вызова функции (Call Stack) в контексте языков программирования. Хочу сказать, что стек вызова не совсем то, что демонстрирует нам большинство материалов. Чтобы окончательно понять что такое стек вызова, необходимо вернуться к азам и рассмотреть как работает стек вызова в операционной системе, почему именно так? Потому что нельзя рассматривать стек вызова функций оторванно от физической памяти и возможностью управления ею и тем самым не надо забывать, что стек вызова функций это не «виртуальная» операция, а конкретное место в памяти выделяемое операционной системой для выполнение программного кода функции, которая была запущена внутренним или внешним API (будь-то браузера, IDE или движка какого либо языка).

    Хочу определить что такое стека вызова функции в низкоуровневых (low-level programming languages) языках (Assembler, C++):

    На примере Ci++:

    (В современных низкоуровневых языках есть современные библиотеки позволяющие поддерживать работу ООП и контролировать стек функции не на физическом уровне, а использованием вывода консоли: Poppy, Pantheios)

    Шаг 1: запуск лейаута кода.

    В лейауте кроме самих инструкций находится дополнительная информация (переменные, декларативные данные, дополнительные технические данные и …)

    Получив первую инструкцию о запуске ОС начинает строить стек. Сначала выделяет физическое место на кластерах памяти и заполняет его данными, но какими:

  • Первый вызов какой либо программной инструкции происходит от модуля ОС, этот запуск так и получил название уровень модуля (это первая инструкция, которая начинает свою работу с модуля памяти ОС) (далее приведено стандартное название в языках main());
  • Инструкция получена, создается сам стек в котором основная часть - выделенное место для выполнения операций, а сегмент стека для хранения данных для возврата результата выполнения функции.

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

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

    Инструкция возврата вызова, это не место вызова этой функции, а следующая за ней инструкция.

    Сегмент стека (который мы сейчас по простоте называем стеком вызова) это отдельный участок памяти, в котором храняться инструкции о возврате результатов вызванных функции. Используется метод: кто пришел последний - уйдет первый (LIFO).

    В языках подобных С++ имплементированы два метода: Call и Ret, Call создает инструкцию с адресом(в бинарной нотации) и кладет на верхний уровень стека, Ret не содержит адреса, этот метод просто забирает верхнюю инструкцию и возвращает нас по указанному адресу. Сам стек, после возвращения данных и выполнения функций уничтожается и данные из него недоступны.

    На рисунке 2 схематично нарисован сегмент стека вызова функций и указаны локальные переменные и инструкции по возврату.

    Далее необходимо понять, как высокоуровневые языки реализовывают работу стека вызова. Давайте рассмотрим два случая: языки использующие многопоточность и однопоточные языки. Почему именно такие варианты? Потому что именно многопоточность (multithreading) и однопоточность определяет взаимодействие виртуальной и физической памяти ресурса.

    Языки использующие многопоточность рассмотрим на примере Java.

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

    В движок java имплементирована специальная коллекция Stack, у которой есть два метода push(добавит данные) и pop(удалить данные). Виртуальная машина Javaведет запись всех вызванных функций и с каждым вызовом функции создает StackTraceElement. После завершения выполнения функции StackTraceElement уничтожается, поэтому информация о стеке вызовов всегда актуальная. С Помощью метода getMethodName можно всегда получить информацию о методе верхнего элемента StackTraceElement, а значит о методе вызова функции.

    На рисунке представлен стек вызова с переменными и методами объектов, реализованных в Java

    Javascript: Javascript однопоточный скриптовый язык использующий очередь функций обратного вызова(callback functions queue) с методом FIFO (первый пришел - первый уйдешь).

    Два варианта взаимодействия физической и виртуальной памяти в Javascript:

  • Работа JS в браузере (цикл событий - event loop с очередью исполняемых колбэков). За переполнением стека (опять же того сегмента с информацией о адресе возврата) тут следит как ОС так и API браузера и V8 двигатель JS. У API Google Chrome есть метод, который после 16 тыс записей стек-кадров (stack frames) выдает сообщение о переполнении стека. после этого браузер выводит сообщение об ошибке или невозможности получить данные для возврата функции и очистки стека. (Источник: ;)
  • Сам стек как и базовые использует LIFO, а очередь колбэков - FIFO. Так же в V8 имплементированы методы push и pop, декларация идентична методам, реализованным в Java.

    Таймеры в JS

    Методы Javascript использующие таймеры использующие API OS и таймеры которые работают на API браузера. Таймеры всегда передвигают выполнение функции-колбэка в конец стека вызовов. Что это значит? Это значит, что в браузере выполняя методы setTimeout, setInterva сначала запускается таймер, а лишь потом функция-колбэк попадает в очередь ожидания. Если в момент попадания колбэка стек переполнен, колбэку приходится ждать своей очереди. Таймеры запущенные вне браузера реализованы на основании либо внутренних методов V8 либо при обращении к API окружения (API OS).

    Особенностью языка Javascript является технология ajax или иначе возможность отправки асинхронных запросов (XMLHTTPRequest) и выполнения каких либо операций без задержки выполнения основного кода. XMLHTTPRequest может быть отправлен синхронно, но при этом весь остальной код будет дожидаться либо результата отправки запроса, что порой нецелесообразно. Технология ajax реализована исключительно в браузерах, что делает выделяет Javascript из группы языков использующих async. Асинхронные запросы в JS используют цикл событий с очередью колбэков (event loop with callback queue). В остальных языках программирования тоже есть возможность запуска асинхронного кода (Python (asyncio), C#(async/await)), что позволяет выполнять какие-то операции без задержки выполнения основных функций, принципом тут тоже выступает цикл событий, где функция колбэк помещается в очередь ожидания. Принцип работы очереди - FIFO.

    Итак, у вас и у вашего партнера появилась замечательная бизнес-идея. Верно?

    Вы постоянно добавляете в уме все новые и новые возможности.

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

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

    И наконец, в один прекрасный день вы решили: “Сделаем это!”. И вот вы уже пытаетесь разобраться как реализовать бизнес-логику своего приложения, ту киллер-фичу, которая будет двигать продукт вперед. У вас есть идея как это сделать, и вы знаете, что способны на это.

    И вот вы говорите: “Готово! Работает!” У вас есть успешный прототип! Осталось только упаковать его в веб приложение.

    “Окей, сделаем сайт,” говорите вы.

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

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

    “Я перегружен”, говорите вы и чувствуете себя перегруженным. Энергия уже не та, что была в начале. Вы пытаетесь собраться с мыслями, но работы слишком много.

    Прототип медленно блекнет и умирает.

    Предложение

    После того, как я забросил кучу идей по похожим причинам, я решил спроектировать решение для этой проблемы. Я назвал этот проект ‘Init ’ (или init.js).

    Основная идея – использовать один проект для старта любого проекта, дать возможность разработчику или техническому руководителю принять все основные решения за раз и получить подходящий начальный шаблон, основанный на них. Я знаю, многие скажут “Нельзя применить одно решение ко всем проблемам” (haters gonna hate). И они, возможно, правы. Но мы можем постараться создать подходящее в целом решение, и, на мой взгляд, Init с этой задачей справился.

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

      Компоненты

      Компонентное представление – одна из ключевых характеристик любой системы, так как она позволяет повторно использовать компоненты программного обеспечения в нескольких проектах, что является основной целью Init. Но компонентное представление содержит в себе побочный эффект – заменяемость, которая станет нашим основным союзником в борьбе с различными проблемами, решение которых “почти” одинаково.

      Простота разработки

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

      Сообщество

      Какую бы платформу вы не выбрали, убедитесь, что вокруг нее существует большое сообщество. Такое, которое может помочь вам с большинством стандартных и нестандартных проблем. Помните: jQuery, возможно, не самая быстрая , чистая , и элегантная библиотека, но она – победитель, благодаря своему сообществу .

    Я покажу как принимал решения при создании Init, не забывая про эти цели.

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

    Но эти идеи не были новыми, почему же они стали так популярны с приходом Node.js? Простое неблокирующее программирование достигается несколькими способами. Пожалуй, самый простой это использовать обратные вызовы (callbacks) и цикл событий –- event loop . В большинстве языков это непростая задача: если обратные вызовы это довольно распространенная функция, то цикл событий – нет, и в какой-то момент вы оказываетесь в схватке с внешними библиотеками (например: Python, с Tornado). Но в JavaScript обратные вызовы это часть языка, как и цикл событий, и каждый программист, который хотя бы пробовал JavaScript, знаком с ними (или как минимум использовал их, даже если не до конца понимал что такое event loop).

    Внезапно, любой стартап на Земле может повторно использовать разработчиков (читай: ресурсы) и на клиентской и на серверной стороне, решая кадровую проблему , “Нам нужен гуру Питона”.

    Да, альтернативы JavaScript’у рождаются каждый день, например, CoffeeScript , TypeScript и миллионы языков, которые компилируются в JavaScript . Эти альтернативы могут быть полезными на этапах разработки (), но им не удастся заменить JavaScript в долгосрочной перспективе по двум причинам: их сообщества никогда не станут больше, и их лучшие возможности будут реализованы в ECMA Script (читай: JavaScript). JavaScript это не язык ассемблера, это высокоуровневый язык программирования с исходных кодом, который вы можете понять, так что вы должны понять его.

    К сожалению, вынужден признаться, что у меня очень мало опыта с Angular.js, так что я исключу его из этой дискуссии. Итак, Ember.js и Backbone.js представляют собой два разных пути для решения одной проблемы.

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

    В зависимости от размера приложения, решение может быть таким же простым, как анализ отношения “используемые функции / доступные функции”. Оно даст вам хорошую подсказку.

    В случае с Init, я хотел покрыть большинство сценариев, поэтому выбрал Backbone.js для простого создания SPA, с Backbone.Marionette.View для компонентизации. В такой схеме каждый компонент - это простое приложение, а конечный продукт может быть настолько комплексным насколько я захочу.

    Стилизация – это тоже большая задача, но мы в очередной раз можем рассчитывать на фреймворки. Для CSS нет ничего лучше