> Часть 2: Как работает JS: о внутреннем устройстве V8 и оптимизации кода

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

Анализ статистики GitHub показывает, что по показателям активных репозиториев и push-запросов, JavaScript находится на первом месте, да и в других категориях он показывает довольно высокие позиции.


Статистические сведения по JavaScript с GitHub

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

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

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

Обзор


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

Здесь мы поговорим, на довольно высоком уровне, о выполнении JS-кода. Зная о том, что, на самом деле, происходит при выполнении JavaScript, вы сможете писать более качественные программы, которые выполняются без «подвисаний» и разумно используют имеющиеся API.

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

Если вы — опытный JS-разработчик, надеемся, этот материал поможет вам лучше понять, как на самом деле работает то, чем вы пользуетесь каждый день.

Движок JavaScript


V8 от Google — это широко известный JS-движок. Он используется, например, в браузере Chrome и в Node.js. Вот как его, очень упрощённо, можно представить:


Упрощённое представление движка V8

На нашей схеме движок представлен состоящим из двух основных компонентов:

  • Куча (Memory Heap) — то место, где происходит выделение памяти.
  • Стек вызовов (Call Stack) — то место, куда в процессе выполнения кода попадают так называемые стековые кадры.

Механизмы времени выполнения


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

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


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

Итак, помимо движка у нас есть ещё очень много всего. Скажем — так называемые Web API, которые предоставляет нам браузер — средства для работы с DOM, инструменты для выполнения AJAX-запросов, нечто вроде функции setTimeout, и многое другое.

Стек вызовов


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

Стек вызовов — это структура данных, которая, говоря упрощённо, записывает сведения о месте в программе, где мы находимся. Если мы переходим в функцию, мы помещаем запись о ней в верхнюю часть стека. Когда мы из функции возвращаемся, мы вытаскиваем из стека самый верхний элемент и оказываемся там, откуда вызывали эту функцию. Это — всё, что умеет стек.

Рассмотрим пример. Взгляните на следующий код:

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);

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

Стек вызовов в ходе выполнения программы

Каждая запись в стеке вызовов называется стековым кадром.

На механизме анализа стековых кадров основана информация о стеке вызовов, трассировка стека, выдаваемая при возникновении исключения. Трассировка стека представляет собой состояние стека в момент исключения. Взгляните на следующий код:

function foo() {
    throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
    foo();
}
function start() {
    bar();
}
start();

Если выполнить это в Chrome (предполагается, что код находится в файле foo.js), мы увидим следующие сведения о стеке:


Трассировка стека после возникновения ошибки

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

function foo() {
    foo();
}
foo();

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

Переполнение стека

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


Превышение максимального размера стека вызовов

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

Однако, и у исполнения кода в однопоточном режиме тоже есть определённые ограничения. Учитывая то, что у JavaScript имеется один стек вызовов, поговорим о том, что происходит, когда программа «тормозит».

Параллельное выполнение кода и цикл событий


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

«А в чём тут проблема?», — спросите вы. Проблема заключается в том, что до тех пор, пока в стеке вызовов имеется выполняющаяся функция, браузер не может выполнять другие задачи — он оказывается заблокированным. Это означает, что браузер не может выводить ничего на экран, не может выполнять другой код. Он просто останавливается. Подобные эффекты, например, несовместимы с интерактивными интерфейсами.

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


Браузер предлагает завершить выполнение страницы

Пользователям подобные вещи точно не понравятся.

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

Итоги


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

Уважаемые читатели! Этот материал — первый в серии «How JavaScript Works» из блога SessionStack. Уже опубликован второй — посвящённый особенностям V8 и техникам оптимизации кода. Как по-вашему, стоит ли его переводить?

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


  1. saag
    05.09.2017 13:53
    +7

    «Как по-вашему, стоит ли его переводить?»
    Да, конечно!


    1. Aries_ua
      05.09.2017 14:59

      Поддерживаю. Ждем продолжения.


    1. Erkon
      06.09.2017 07:57
      -1

      +



  1. worldxaker
    05.09.2017 14:29
    +1

    ждем продолжения


  1. Fractalzombie
    05.09.2017 14:30
    +1

    Обязательно стоит.


  1. sergx87
    05.09.2017 14:56
    +7

    Ну блин, вся статья — затравочка для другой статьи :-)


  1. KleinenberG
    05.09.2017 15:12

    стоит


  1. EreminD
    05.09.2017 15:43
    +5

    Спасибо большое
    Честно говоря, конкретно эта статья не сказать, что полезна. Просто рассказ о том, что такое стэк. А вот перевод про V8 будем ждать.


  1. hitman51
    05.09.2017 17:02
    -1

    Жду продолжения) спасибо за труд)


  1. Woislav
    05.09.2017 17:02
    +2

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


  1. belousovsw
    06.09.2017 07:57
    +1

    Да, обязательно стоит! Не слишком много в интернете информации о том как все внутри устроено, а это очень полезная информация.
    Спасибо Вам за труд!


  1. dmitry_pacification
    06.09.2017 07:57
    +1

    Очень рад буду почитать перевод. Спасибо за статью!


  1. Hacksli
    06.09.2017 08:44

    Лайк!


  1. EvilBeaver
    06.09.2017 08:52
    +1

    Вот так всегда, на самом интересном месте...


    Обязательно пишите продолжение!


  1. parizene
    06.09.2017 18:35

    я бы посоветовал данный сайт для наглядности работы js, как и видео к нему


    1. neverovski
      07.09.2017 11:57

      Лайк за ссылку, я тоже поддерживаю


  1. Deroy
    07.09.2017 11:57

    Устройство JS — интерактивная презентация: Loupe


    в дополнение к статье хорошо зайдет.


  1. sargsarmen
    07.09.2017 11:57

    Жду продолжения


  1. Adronex
    07.09.2017 11:57

    Смущает статистика за 4-й квартал 2014-го года, а так очень интересно, да.


  1. dzen_vito
    07.09.2017 11:57

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

    Очень надеюсь, что автор затронет и эту тему


  1. Art4es
    07.09.2017 11:57

    «What the heck is event loop»


  1. 4reddy
    07.09.2017 11:57

    Однозначно стоит. Для новичков — самое оно.


  1. Cake_Seller
    07.09.2017 18:44

    Как по мне стоило бы всё таки упомянуть про Web Workers, чтобы было понятно, что асинронный код не единственный способ выполнять задачи требующие много процессорного времени не блокируя основной поток браузера и тем самым не блокируя ввод/вывод, рендеринг.


  1. m0sk1t
    07.09.2017 19:04

    Опрос бы в статью не помешал)


  1. IAKO
    08.09.2017 17:47

    Только разогрелся и настроился постигать, как статья закончилась.