Статьи

Анатомия современного JavaScript-приложения

Эта статья включена в нашу книгу « JavaScript: лучшие практики» . Будьте в курсе быстро меняющихся лучших практик современного JavaScript.

Нет сомнений, что экосистема JavaScript быстро меняется. Мало того, что с появлением ES2015 (он же ES6) претерпел быстрые изменения и не только новые инструменты и структуры, но и сам язык. Понятно, что было написано много статей с жалобами на то, как трудно в наши дни освоить современную разработку JavaScript.

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

Женщина, играющая в натуральную величину в Операции; метафора для многих компонентов современного приложения JavaScript "

Примечание о Node.js

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

Появление Node.js оказало значительное влияние на экосистему JavaScript, представив менеджер пакетов npm и популяризовав формат модуля CommonJS. Разработчики начали создавать более инновационные инструменты и разрабатывать новые подходы, чтобы стирать грань между браузером, сервером и нативными приложениями.

JavaScript ES2015 +

В 2015 году была выпущена шестая версия ECMAScript — спецификация, определяющая язык JavaScript — под названием ES2015 (до сих пор часто упоминаемая как ES6). Эта новая версия включает существенные дополнения к языку, что делает его более простым и выполнимым для создания амбициозных веб-приложений. Но улучшения не прекращаются с ES2015; каждый год выпускается новая версия.

Объявление переменных

В JavaScript теперь есть два дополнительных способа объявления переменных: let и const .

letvar Хотя varlet

 // ES5
for (var i = 1; i < 5; i++) {
  console.log(i);
}
// <-- logs the numbers 1 to 4
console.log(i);
// <-- 5 (variable i still exists outside the loop)

// ES2015
for (let j = 1; j < 5; j++) {
  console.log(j);
}
console.log(j);
// <-- 'Uncaught ReferenceError: j is not defined'

Использование const Для примитивных значений, таких как строки и числа, это приводит к чему-то похожему на константу, поскольку вы не можете изменить значение после того, как оно было объявлено:

 const name = 'Bill';
name = 'Steve';
// <-- 'Uncaught TypeError: Assignment to constant variable.'

// Gotcha
const person = { name: 'Bill' };
person.name = 'Steve';
// person.name is now Steve.
// As we're not changing the object that person is bound to, JavaScript doesn't complain.

Функции стрелок

Функции со стрелками обеспечивают более чистый синтаксис для объявления анонимных функций (лямбда-выражений), отбрасывания ключевого слова functionreturn Это может позволить вам написать код функционального стиля более приятным способом:

 // ES5
var add = function(a, b) {
  return a + b;
}

// ES2015
const add = (a, b) => a + b;

Другая важная особенность функций со стрелками заключается в том, что они наследуют значение this

 function Person(){
  this.age = 0;

  // ES5
  setInterval(function() {
    this.age++; // |this| refers to the global object
  }, 1000);

  // ES2015
  setInterval(() => {
    this.age++; // |this| properly refers to the person object
  }, 1000);
}

var p = new Person();

Улучшенный синтаксис класса

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

 class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

Обещания / Асинхронные функции

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

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

ES2017 представил асинхронные функции (иногда называемые async / await), которые вносят улучшения в эту область, позволяя обрабатывать асинхронный код так, как если бы он был синхронным:

 async function doAsyncOp () {
  var val = await asynchronousOperation();
  console.log(val);
  return val;
};

Модули

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

Есть и другие функции, о которых мы не будем здесь говорить, но мы рассмотрели некоторые основные различия, которые вы, вероятно, заметите, глядя на современный JavaScript. Вы можете проверить полный список с примерами на странице Learn ES2015 на сайте Babel , которая может оказаться полезной для ознакомления с языком. Некоторые из этих функций включают в себя строки шаблонов, переменные и константы в области блока, итераторы, генераторы, новые структуры данных, такие как Map и Set, и многое другое.


Чтобы узнать больше о ES2015, ознакомьтесь с нашим Премиум курсом: погружение в ES2015 .


Кодирование

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

Вы можете проверить ESLint , который является одним из самых популярных и поддерживает ES2015 +.

Модульный код

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

CommonJS модули

За прошедшие годы появилось несколько форматов модулей, самым популярным из которых является CommonJS . Это формат модуля по умолчанию в Node.js, и его можно использовать в клиентском коде с помощью компоновщиков модулей, о которых мы вскоре поговорим.

Он использует объект modulerequire()

 // lib/math.js
function sum(x, y) {
  return x + y;
}

const pi = 3.141593

module.exports = {
  sum: sum,
  pi: pi
};


// app.js
const math = require("lib/math");

console.log("2π = " + math.sum(math.pi, math.pi));

Модули ES2015

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

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

Вот пример:

 // lib/math.js

export function sum(x, y) {
  return x + y;
}
export const pi = 3.141593;

Здесь у нас есть модуль, который экспортирует функцию и переменную. Мы можем включить этот файл в другой и использовать эти экспортированные функции:

 // app.js

import * as math from "lib/math";

console.log("2π = " + math.sum(math.pi, math.pi));

Или мы также можем быть конкретными и импортировать только то, что нам нужно:

 // otherApp.js

import {sum, pi} from "lib/math";

console.log("2π = " + sum(pi, pi));

Эти примеры были взяты с веб-сайта Babel . Для получения более подробной информации, ознакомьтесь с Понимание модулей ES6 .

Управление пакетами

Другие языки уже давно имеют свои собственные репозитории и менеджеры пакетов, чтобы упростить поиск и установку сторонних библиотек и компонентов. Node.js поставляется с собственным менеджером пакетов и репозиторием, npm . Хотя есть и другие доступные менеджеры пакетов, npm стал де-факто менеджером пакетов JavaScript и считается самым большим реестром пакетов в мире.

В репозитории npm вы можете найти сторонние модули, которые вы можете легко загрузить и использовать в своих проектах с помощью одной команды npm install <package> Пакеты загружаются в локальный каталог node_modules

Загруженные вами пакеты могут быть зарегистрированы как зависимости вашего проекта в файле package.json вместе с информацией о вашем проекте или модуле (который сам по себе может быть опубликован в виде пакета на npm).

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

Пример файла package.json

 {
  "name": "demo",
  "version": "1.0.0",
  "description": "Demo package.json",
  "main": "main.js",
  "dependencies": {
    "mkdirp": "^0.5.1",
    "underscore": "^1.8.3"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Sitepoint",
  "license": "ISC"
}

Инструменты сборки

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

Модуль комплектации

При написании чистого многократно используемого кода с модулями ES2015 / CommonJS нам нужен какой-то способ загрузки этих модулей (по крайней мере, пока браузеры не поддерживают загрузку модулей ES2015 изначально). Включение набора тегов сценариев в ваш HTML на самом деле нереальный вариант, поскольку он быстро станет громоздким для любого серьезного приложения, а все эти отдельные HTTP-запросы могут снизить производительность.

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

В настоящее время есть несколько популярных вариантов для этого, наиболее популярными из которых являются Webpack , Browserify и Rollup.js . Вы можете выбрать один или другой в зависимости от ваших потребностей.


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


Transpilation

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

Чтобы наш современный JavaScript работал, нам нужно перевести код, который мы пишем, в его эквивалент в более ранней версии (обычно ES5). Стандартным инструментом для этой задачи является Babel — компилятор, который переводит ваш код в совместимый код для большинства браузеров. Таким образом, вам не нужно ждать, пока поставщики реализуют все; Вы можете просто использовать все современные функции JS.

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

Сборка систем и задач

Объединение и транспортировка модулей — это только два процесса сборки, которые нам могут понадобиться в наших проектах. Другие включают минимизацию кода (для уменьшения размеров файлов), инструменты для анализа и, возможно, задачи, которые не имеют ничего общего с JavaScript, такие как оптимизация изображений или предварительная обработка CSS / HTML.

Управление задачами может стать трудоемким занятием, и нам нужен способ автоматизировать его, чтобы выполнять все с помощью более простых команд. Двумя наиболее популярными инструментами для этого являются Grunt.js и Gulp.js , которые обеспечивают способ упорядочивания ваших задач в группы.

Например, у вас может быть такая команда, как gulp build Вместо того, чтобы запоминать три команды и связанные с ними аргументы по порядку, мы просто выполняем одну, которая автоматически обрабатывает весь процесс.

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


Дальнейшее чтение: Введение в Gulp.js.


Архитектура приложений

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

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

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

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

Наиболее распространенная высокоуровневая архитектура для веб-приложений называется SPA , что означает одностраничное приложение . SPA — это большие фрагменты JavaScript, которые содержат все, что нужно приложению для правильной работы. Пользовательский интерфейс отображается полностью на стороне клиента, поэтому перезагрузка не требуется. Единственное, что изменяется, — это данные внутри приложения, которые обычно обрабатываются с помощью удаленного API через Ajax или другой асинхронный метод связи.

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

Универсальные / Изоморфные Приложения

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

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

развертывание

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

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

Вы можете иметь такую ​​структуру каталогов:

 ├── dist
│   ├── app.js
│   └── index.html
├── node_modules
├── src
│   ├── lib
│   │   ├── login.js
│   │   └── user.js
│   ├── app.js
│   └── index.html
├── gulpfile.js
├── package.json
└── README

Таким образом, у вас есть все файлы приложения в каталоге srclib

Затем вы можете запустить Gulp, который выполнит инструкции из gulpfile.jsdist

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

Теперь вы можете просто загрузить файлы из каталога dist

Развитие команды

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

Однако хранение встроенных файлов в хранилище может привести к ошибкам, если несколько разработчиков работают вместе, и вы можете захотеть сохранить все в чистоте от артефактов сборки. К счастью, есть более эффективный способ решения этой проблемы: вы можете поместить сервис, такой как Jenkins , Travis CI , CircleCI и т. Д., В центр процесса, чтобы он мог автоматически строить ваш проект после каждой фиксации в репозитории. Разработчикам нужно только беспокоиться о внесении изменений в код, не создавая проект каждый раз. Хранилище также поддерживается в чистоте от автоматически сгенерированных файлов, и, в конце концов, у вас все еще есть встроенные файлы, доступные для развертывания.

Вывод

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

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

Какой у вас опыт изучения современной разработки JavaScript? Есть ли что-то, чего я не коснулся здесь, что вы хотели бы увидеть в будущем? Я хотел бы услышать от вас!