Анализ принципа работы JavaScript

внешний интерфейс JavaScript Chrome
Анализ принципа работы JavaScript

Когда дело доходит до принципа работы JavaScript, естественно избегать таких понятий, как движок JS, работающий контекст, один поток, цикл событий, управление событиями, функция обратного вызова и так далее. Основные справочные статьи в этой статье [1,2].

Чтобы лучше понять, как работает JavaScript, сначала необходимо понять следующие концепции.

  • JS-движок
  • Время выполнения (контекст выполнения)
  • Стек вызовов
  • EVENT LOOP (Цикл событий)
  • Перезвоните

1.JS Engine

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

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

JS Engine 结构

Как и виртуальная машина JVM, движок JS также имеет концепцию кучи (Memory Heap) и стека (Call Stack).

  • куча. Место, используемое для хранения вызовов методов, и основные типы данных (такие как var a = 1) также хранятся в стеке и будут автоматически уничтожены, когда вызов метода завершится (Push-->After the method is call -- > Вскрыть стек).

  • куча. Пространство памяти, выделенное объектам в движке JS, помещается в кучу. Например, var foo = {name: 'foo'}, тогда объект, на который указывает этот foo, сохраняется в куче.

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

Что касается закрытия, то речь идет оCaptured Variables. мы знаемLocal Variablesявляется самым простым случаем и хранится непосредственно в стеке. а такжеCaptured Variablesдля существования случая закрытия иwith,try catchпеременные ситуации.

function foo () {
  var x; // local variables
  var y; // captured variable, bar中引用了y

  function bar () {
  // bar 中的context会capture变量y
    use(y);
  }

  return bar;
}

Как и выше, переменнаяyсуществовать сbar()в закрытии , такyэто захваченная переменная, которая хранится в куче.

2.RunTime

JS в браузере может вызывать API, предоставляемый браузером, например объект окна, API, связанный с DOM, и т. д. Эти интерфейсы не предоставляются движком V8, но существуют в браузере. Поэтому, проще говоря, для этих связанных внешних интерфейсов JS может вызывать их во время выполнения, а также цикл событий JS (Event Loop) и очередь событий (Callback Queue), которые называются RunTime. В некоторых местах основная библиотека core lib, используемая JS, также считается частью RunTime.

The RunTime

Точно так же в Node.js API, предоставляемый различными библиотеками Node, может называться RunTime. Таким образом, можно понять, что и Chrome, и Node.js используют один и тот же движок V8, но имеют разные среды выполнения [4].

3.Call Stack

JS предназначен для работы в одном потоке, потому что JS в основном используется для реализации многих операций, связанных с взаимодействием, таких как операции, связанные с DOM.Если он многопоточный, это вызовет сложные проблемы с синхронизацией. Таким образом, JS с момента своего рождения является однопоточным, а основной поток используется для операций рендеринга, связанных с интерфейсом.(Почему основной поток, потому что HTML5 предоставляет Web Worker, независимый фоновый JS, используемый для обработки некоторых трудоемких операций с данными. Поскольку он не будет изменять соответствующие элементы DOM и страницы, он не влияет на производительность страницы), если есть блокировка, это приведет к зависанию браузера.

Если рекурсивный вызов не имеет условия завершения и представляет собой бесконечный цикл, это приведет к нехватке памяти стека вызовов и переполнению, например:

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

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

call_stack_overflow

Что делать, если поток JS сталкивается с трудоемкими операциями, такими как чтение файлов и запросы AJAX? Здесь JS использует функцию обратного вызова Callback для обработки.

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

4.Event Loop & Callback

JS обрабатывает трудоемкие задачи асинхронно с помощью обратных вызовов. Простой пример:

var result = ajax('...');
console.log(result);

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

ajax('...', function(result) {
    console.log(result);
})

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

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

Так, например, когда ваша программа JavaScript делает Ajax-запрос для получения некоторых данных с сервера, вы настраиваете код «ответа» в функции («обратный вызов»), и JS Engine сообщает среде хостинга: «Эй, я собираюсь приостановить выполнение на данный момент, но когда вы закончите с этим сетевым запросом, и у вас есть какие-то данные, пожалуйста, вызовите эту функцию обратно».

The browser is then set up to listen for the response from the network, and when it has something to return to you, it will schedule the callback function to be executed by inserting it into the event loop.

Приведенные выше два отрывка взяты изHow JavaScript works, в котором популярным способом объясняется, как JS вызывает функции обратного вызова для достижения асинхронной обработки.

Так что же такое цикл событий?

Цикл событий выполняет только одну функцию, отвечая за мониторинг стека вызовов и очереди обратного вызова. Когда стек вызовов в Call Stack запускается и становится пустым, Event Loop помещает первое событие (фактически callback-функцию) из Callback Queue в стек вызовов и выполняет его, а затем непрерывно выполняет эту операцию в цикле.

setTimeoutПримеры и соответствующие динамические диаграммы цикла событий:

console.log('Hi');
setTimeout(function cb1() { 
    console.log('cb1');
}, 5000);
console.log('Bye');

event_loop动态图

setTimeoutЕсть одна вещь, на которую следует обратить внимание, например, приведенный выше пример задерживает выполнение 5 с. Это не строго 5 с. Точнее, он будет выполнен после как минимум 5 с. Поскольку веб-API установит 5-секундный таймер, функция обратного вызова будет добавлена ​​в очередь по истечении времени. В это время функция обратного вызова может выполняться не сразу, поскольку в очереди могут быть другие функции обратного вызова, добавленные ранее, и Вы также должны дождаться, пока стек вызовов не станет пустым, прежде чем принимать обратный вызов из очереди для выполнения.

так обычноsetTimeout(callback, 0)Способ сделать это — запустить функцию обратного вызова сразу после введения обычного вызова.

console.log('Hi');
setTimeout(function() {
    console.log('callback');
}, 0);
console.log('Bye');
// 输出
// Hi
// Bye
// callback

Говоря о каштане, который может ошибаться:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000 * i);
}
	
// 输出:5 5 5 5 5

Каштан выше не выводит 0, 1, 2, 3, 4. Первая реакция должна быть такой. Но после прочтения временной петли JS это должно быть легко понять.

Стек вызовов выполняется первымfor(var i = 0; i < 5; i++) {...}метод, таймер внутри напрямую поместит функцию обратного вызова в очередь событий, когда время истечет, а после выполнения цикла for она будет извлечена и помещена в стек вызовов по очереди. Когда цикл for завершает выполнение,iЗначение стало равным 5, поэтому окончательный вывод равен всем 5.

Вы также можете посмотреть на таймерэта интересная статья

Наконец, о цикле событий вы можете обратиться к этомувидео. Цикл событий, упомянутый до сих пор, — это цикл событий во внешнем браузере.Подробнее о цикле событий Nodejs см. в другой моей статье.Node.js design pattern : Reactor (Event Loop). Разницу между ними можно посмотреть в этой статье.Цикл событий, который вы не знаете, который суммирует и сравнивает два цикла событий.

Суммировать

Наконец, подводя итог, принцип работы JS в основном имеет следующие аспекты:

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

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

  • Функция обратного вызова вызывается путем добавления ее в очередь событий и ожидания извлечения цикла событий и помещения его в стек вызовов. Только когда Event Loop отслеживает, что стек вызовов пуст, он берет функцию обратного вызова из заголовка очереди из очереди событий и помещает ее в стек вызовов.

основная ссылка

1.How JavaScript works: an overview of the engine, the runtime, and the call stack

2.How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await

3.Philip Roberts: What the heck is the event loop anyway?