Доброго времени суток, друзья!

Продолжаю публиковать перевод этого руководства по Node.js.

Другие части:
Часть 1
Часть 2
Часть 3

Движок JavaScript V8


V8 — название движка JavaScript, поддерживаемого Google Chrome. Эта та штука, которая берет ваш JavaScript-код и выполняет его в браузере.

Другими словами, V8 представляет собой среду выполнения JavaScript. DOM и другие веб-API также предоставляются браузером.

Движок JavaScript не зависит от браузера. Именно это привело к появлению Node.js. V8 был выбран в качестве движка для Node.js в 2009, и с ростом популярности Node.js V8 стал основой для большого количества серверного JavaScript.

Экосистема Node.js огромна и благодаря V8 мы имеем возможность создавать настольные приложения, например, с помощью Electron.

Другие JS движки

Другие браузеры имеют собственные JavaScript-движки:


и т.д.

Во всех этих движках реализован стандарт ECMA ES-262 (ECMAScript), стандарт, используемый JavaScript.

О производительности

V8 написан на C++ и продолжает развиваться. Он работает во всех операционных системах.

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

V8 постоянно эволюционирует, как и другие JavaScript-движки, для повышения скорости веба и экосистемы Node.js.

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

Компиляция

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

Это происходит с 2009 года, когда компилятор JavaScript SpiderMonkey был добавлен в Firefox 3.5.

JavaScript компилируется V8 «на лету» (just-in-time, JIT, динамическая компиляция) для ускорения его выполнения.

Это может показаться нелогичным, однако с появлением Google Maps в 2004, JavaScript эволюционировал от языка, на котором пишут небольшие блоки кода, до языка, на котором создаются полноценные приложения, состоящие из сотен и тысяч строк кода, выполняющегося в браузере.

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

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

Запуск Node.js-скриптов посредством командной строки


Стандартным способом запуска программ на Node.js является выполнение глобальной команды node и передачи имени исполняемого файла.

Если основной файл вашего Node.js-приложения называется app.js, вы можете вызвать его следующим образом:

node app.js

При выполнении указанной команды убедитесь, что находитесь в директории с файлом app.js.

Как выйти из Node.js?


Существуют различные способы остановки Node.js-приложения.

При запуске программы посредством терминала, вы можете закрыть его с помощью ctrl-C, однако давайте обсудим программные способы.

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

Основной (глобальный, модуль ядра) модуль process содержит удобный метод, позволяющий программно выйти из Node.js-приложения: process.exit().

Когда Node.js достигает этой строки кода, процесс выполнения программы немедленно завершается.

Это означает, что все выполняющиеся функции обратного вызова, отправленные (незавершенные) запросы, открытый доступ к файловой системе, процессы записи в stdout или stderr — все они будут жестко прерваны.

Если это для вас приемлемо, вы можете передать методу exit() целое число — сигнал к завершению выполнения кода:

process.exit(1)

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

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

Также вы можете присвоить соответствующее значение свойству exitCode:

process.exitCode = 1

и после завершения программы, Node.js вернет этот код.

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

В Node.js мы часто запускаем сервер:

const express = require('express')
const app = express()

app.get('/', (req, res) => {
    res.send('Hi!')
})

app.listen(3000, () => console.log('Server ready'))

Эта программа никогда не завершится. Если вы вызовете process.exit(), все ожидающие или выполняющиеся запросы будут прерваны. Это не круто.

В данном случае необходимо отправить команду-сигнал SIGTERM и обработать ее с помощью обработчика процессорных сигналов (process подключать не нужно, он доступен по умолчанию):

const express = require('express')

const app = express()

app.get('/', (req, res) => {
    res.send('Hi!')
})

const server = app.listen(3000, () => console.log('Server ready'))

process.on('SIGTERM', () => {
    server.close(() => {
        console.log('Process terminated')
    })
})

Что такое сигналы? Сигналы — это система коммуникации POSIX: отправка процессу уведомления о произошедшем событии.

SIGKILL — сигнал о немедленном завершении процесса, аналогичный process.exit().

SIGTERM — сигнал о мягком завершении процесса. Этот сигнал может быть отправлен системой управления процессами, такой как upstart, supervisord и др.

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

process.kill(process.id, 'SIGTERM')

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

Как читать переменные среды в Node.js?


Основной модуль Node.js process имеет свойство env, содержащее все переменные среды, установленные при запуске процесса.

Вот пример получения доступа к переменной среды NODE_ENV, установленной в значение development по умолчанию:

process.env.NODE_ENV // development

Установка значения в production перед выполнением скрипта сообщит Node.js о том, что перед ним продакшн-среда.

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

Как использовать Node.js REPL


Команда node используется для запуска Node.js-скриптов:

node script.js 

Если мы опустим имя файла, то попадем в режим REPL:

node 

REPL (Run Evaluate Print Loop, цикл «чтение-вычисление-вывод») — это среда выполнения кода (обычно, окно терминала), которая принимает выражение, введенное пользователем, и возвращает результат вычисления этого выражения.

Если вы введете node в терминале, произойдет следующее:

>

Терминал перейдет в режим ожидания.

Если быть более точным, терминал в данном случае ожидает ввода какого-либо JavaScript-кода.

Введем следующее:

> console.log('test')
test 
undefined 
>

Первое значение, test, это то, что мы велели вывести в консоль, затем мы получаем undefined — значение, которое вернуло выполнение console.log()

После этого мы можем ввести что-нибудь еще.

Использование tab для автозавершения

REPL является интерактивным.

Если нажать tab при написании кода, REPL попытается завершить написанное, выбирая из определенных ранее или предопределенных переменных.

Объекты JavaScript

Попробуйте ввести имя JavaScript-класса, например, Number, добавьте к нему точку и нажмите tab.

REPL покажет все свойства и методы этого класса:



Глобальные объекты

Вы можете получить список глобальных объектов, введя global. и нажав tab:



Специальная переменная _

Если в конце кода набрать _, будет выведен результат выполнения последней операции.

Команды после точки

REPL содержит некоторые специальные команды, начинающиеся с точки. Вот они:

  • .help — показывает список доступным команд
  • .editor — включает режим редактирования для написания многострочного JavaScript-кода. Для выполнения кода в этом режиме необходимо нажать ctrl-D
  • .break — прекращает ввод многострочного кода. Аналогично нажатию ctrl-C
  • .clear — сбрасывает контекст REPL к пустому объекту, удаляет весь введенный код
  • .load — загружает JavaScript-файл, находящийся в текущей (рабочей) директории
  • .save — сохраняет сессию REPL в файл с указанным именем
  • .exit — выход из REPL. Аналогично двойному нажатию ctrl-C

REPL понимает, что вы вводите многостроный код без вызова .editor.

Например, если вы начали реализовывать итерацию:

[1, 2, 3].forEach(num => {

и нажали enter, REPL перейдет на новую строку с тремя точками в начале, сообщая, что вы можеет продолжить работу с блоком кода:

... console.log(num)
... })

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

Передача аргументов с помощью командной строки


При запуске Node.js-приложения вы можете передать ему любое количество аргументов.

Аргументы могут быть автономными или иметь ключ и значение.

Например:

node app.js joe

или

node app.js name=joe 

От этого зависит то, как вы будете извлекать значение в Node.js-коде.

Для извлечения значений используется встроенный объект process.

В свойстве argv этого объекта содержится массив аргументов, переданных посредством командной строки.

Первый аргумент — это полный путь команды node.

Второй — полный путь исполняемого файла.

Интересующие нас аргументы начинаются с третьей позиции (индекса массива).

Вы можете перебрать аргументы (включая путь node и путь файла) с помощью цикла:

process.argv.forEach((val, index) => {
    console.log(`${index}: ${val}`)
})

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

const args = process.argv.slice(2)

Если у нас имеется один аргумент без индекса (ключа):

node app.js joe

мы можем получить его следующим образом:

const args = process.argv.slice(2)
args[0]

В этом случае:

node app.js name=joe

args[0] — это name=joe, поэтому нам нужно его разобрать. Лучшим способом это сделать является использование библиотеки minimist, предназначенной для работы с аргументами:

const args = require('minimist')(process.argv.slice(2))
args['name'] // joe

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

node app.js --name=joe

Вывод результатов в командную строку с помощью Node.js


Стандартный вывод посредством модуля console

Node.js предоставляет модуль console, содержащий множество очень полезных способов взаимодействия с командной строкой.

Он похож на объект console браузера.

Одним из основных методов данного модуля является console.log(), который выводит в консоль переданную строку.

Если вы передадите объект, он будет преобразован в строку.

Мы можем передавать console.log несколько переменных:

const x = 'x'
const y = 'y'
console.log(x, y)

и Node.js выведет обе.

Мы также можем форматировать строку с помощью спецификаторов:

Например:

console.log('My %s has %d years', 'cat', 2)

  • %s — форматирует переменную как строку
  • %d — форматирует переменную как число
  • %i — приводит переменную к целому числу
  • %o — форматирует переменную как объект

Например:

console.log('%o', Number)

Очистка консоли

console.clear() очищает консоль (поведение зависит от используемой консоли).

Подсчет элементов

console.count() — удобный метод.

Изучите этот код:

const x = 1 
const y = 2 
const z = 3
console.count(
    'The value of x is ' + x +
    ' and has been checked .. how many times?'
)
console.count(
    'The value of x is ' + x +
    ' and has been checked .. how many times?'
)
console.count(
    'The value of y is ' + y +
    ' and has been checked .. how many times?'
)

Счетчик считает количество отображений строки и показывает это количество.

The value of x is 1 and has been checked .. how many times?: 1
The value of x is 1 and has been checked .. how many times?: 2
The value of y is 2 and has been checked .. how many times?: 1  

Так вы можете посчитать количество яблок и апельсинов:

const oranges = ['orange', 'orange']
const apples = ['just one apple']
oranges.forEach(fruit => console.count(fruit))
apples.forEach(fruit => console.count(fruit))

Отображение трассировки стека

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

Вы можете сделать это с помощью console.trace():

const function2 = () => console.trace()
const function1 = () => function2()
function1()

Это выведет в консоль трассировку стека. Вот что мы увидим в командной строке, если выполним приведенный код в Node.js REPL:

Trace
    at function2 (repl:1:33)
    at function1 (repl:1:25)
    at repl:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:44:33)
    at REPLServer.defaultEval (repl.js:239:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:440:10)
    at emitOne (events.js:120:20)
    at REPLServer.emit (events.js:210:7)

Подсчет времени выполнения кода

Вы легко можете посчитать, сколько времени выполнялась функция при помощи time() и timeEnd():

const doSomething = () => console.log('test')
const measureDoingSomething = () => {
    console.time('doSomething()')
    // выполняем какие-либо операции и засекаем время их выполнения
    doSomething()
    console.timeEnd('doSomething()')
}
measureDoingSomething()

stdout и stderr

Как мы знаем, console.log отлично подходит для вывода сообщений в консоль. Это называется стандартным выводом или stdout.

console.error отображает поток stderr.

Данный поток не выводится в консоль, а записывается в журнал ошибок (error log).

Стилизуем вывод

Вы можете раскрасить текст, выводимый в консоль, с помощью обратных последовательностей (escape sequences). Эти последовательности представлют собой набор символов, идентифицирующих цвет.

Например:

console.log('\x1b[33m%s\x1b[0m', 'hi!')

Если набрать приведенный код в Node.js REPL, то hi! будет желтого цвета.

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

Устанавливаем библиотеку посредством npm install chalk и используем ее следующим образом:

const chalk = require('chalk')
console.log(chalk.yellow('hi!'))

Использование chalk.yellow гораздо проще, чем запоминание сложных последовательностей. Это также делает код более читаемым.

Создание индикатора прогресса

Progress — отличная библиотека для создания индикаторов прогресса в терминале. Устанавливаем ее с помощью npm install progress.

Данный сниппет создает индикатор прогресса, состоящий из 10 шагов. Каждые 100 мс выполняется один шаг. При заполнении индикатора мы отключаем счетчик:

const ProgressBar = require('progress')

const bar = new ProgressBar(':bar', { total: 10 })
const timer = setInterval(() => {
    bar.tick()
    if (bar.complete) clearInterval(timer)
}, 100)

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

Продолжение следует…