1. Почему JavaScript однопоточный?
Основной особенностью языка JavaScript является единственное потоки, что означает, что вы можете сделать только одну вещь одновременно. Итак, почему у JavaScript нет нескольких потоков? Это повысит эффективность.
Единственное потоки JavaScript связано с его целью. Как язык сценариев браузера, основная цель JavaScript - взаимодействовать с пользователями и управлять DOM. Это определяет, что он может быть только однопоточным, в противном случае это вызовет очень сложные проблемы синхронизации. Например, предположим, что у JavaScript есть два потока одновременно, один поток добавляет содержимое в определенном узле DOM, а другой поток удаляет этот узел, какой поток должен взять браузер в это время?
Следовательно, чтобы избежать сложности, JavaScript - это единственный поток от его рождения, который стал основной особенностью этого языка и не изменится в будущем.
Чтобы использовать вычислительную мощность многоядерных процессоров, HTML5 предложил стандарт веб-работника, позволяя JavaScript Scripts создавать несколько потоков, но детские потоки полностью управляются основным потоком и не могут управлять DOM. Следовательно, этот новый стандарт не меняет природу отдельного потока JavaScript.
2. Очередь задач
Одиночный потоки означает, что все задачи должны быть в очереди, и предыдущая задача будет выполнена до выполнения следующей задачи. Если предыдущая задача занимает много времени, следующая задача должна ждать.
Если очередь происходит из -за большого количества вычислений, а процессор слишком занят, это было бы хорошо, но много раз процессор простаивает, потому что устройство iO (устройство ввода и вывода) очень медленное (например, данные операции Ajax считывает данные из сети), и вы должны ждать результата, чтобы получить результат до его выполнения.
Дизайнер языка JavaScript понял, что в настоящее время процессор может полностью игнорировать устройство iO, приостановить задачи ожидания и сначала выполнить следующие задачи. Подождите, пока устройство IO не вернет результат, затем развернитесь и продолжайте приостановленную задачу.
Следовательно, JavaScript имеет два метода выполнения: один из них заключается в том, что ЦП выполняет в последовательности, предыдущая задача заканчивается, а затем выполняется следующая задача, которая называется синхронным выполнением; Другим является то, что процессор пропускает задачи с длительным временем ожидания и сначала обрабатывает последующие задачи, которые называются асинхронным исполнением. Программисты независимо выбирают, какой метод выполнения принять.
В частности, эксплуатационный механизм асинхронного исполнения заключается в следующем. (То же самое верно для синхронного выполнения, так как его можно рассматривать как асинхронное выполнение без асинхронных задач.)
(1) Все задачи выполняются в основном потоке, чтобы сформировать стек контекста выполнения.
(2) В дополнение к основному потоку есть также «очередь задач». Система помещает асинхронные задачи в «очередь задач», а затем продолжает выполнять последующие задачи.
(3) Как только все задачи в «стеке выполнения» будут выполнены, система будет читать «очередь задач». Если в это время асинхронная задача завершила состояние ожидания, она введет стек выполнения из «очереди задач» и резюме выполнения.
(4) Основной поток продолжает повторять третий шаг выше.
Следующий рисунок представляет собой схематическую диаграмму основного потока и очереди задач.
Пока основной поток пуст, он будет читать «очередь задач». Это работающий механизм JavaScript. Этот процесс будет повторяться непрерывно.
3. События и функции обратного вызова
«Очередь задачи» - это, по сути, очередь событий (также понимается как очередь сообщений). Когда устройство iO выполняет задачу, оно добавляет событие в «очередь задач», указывая на то, что соответствующие асинхронные задачи могут ввести «стек выполнения». Основной поток считывает «очередь задач», что означает чтение того, какие события находятся внутри.
События в «очереди задач» включают события в дополнение к событиям с устройств iO, а также события, сгенерированные пользователями (например, клики мыши, прокрутка страниц и т. Д.). Пока указана функция обратного вызова, эти события будут входить в «очередь задач», когда они возникают, и ждать чтения основного потока.
Так называемый «обратный вызов»-это код, который будет повешен основным потоком. Асинхронные задачи должны указать функцию обратного вызова. Когда асинхронная задача возвращается из «очереди задачи» в стек выполнения, функция обратного вызова будет выполнена.
«Очередь задачи»-это первая в первую очередь структуру данных, с событиями, которые ранжируются первыми и предпочитают вернуться в основной теме. Основной процесс чтения в основном является автоматическим. Пока стек выполнения очищен, первое событие в «очереди задачи» автоматически вернется в основной поток. Тем не менее, из -за функции «таймер», упомянутой позже, основной поток должен проверить время выполнения, и некоторые события должны вернуться к основному потоку в указанное время.
4. Петля событий
Основной поток считывает события из «очереди задач». Этот процесс непрерывно зацикливается, поэтому весь работающий механизм также называется цикла событий.
Чтобы лучше понять цикл событий, пожалуйста, посмотрите картинку ниже (цитируется из речи Филиппа Робертса «Помощь, я застрял в петле событий»).
На рисунке выше, когда работает основной поток, он генерирует кучу и стек. Код в стеке вызывает различные внешние API, которые добавляют различные события (нажимайте, загружаются, сделаны) в «очередь задач». Пока код в стеке выполняется, основной поток будет считывать «очередь задач» и выполнять функции обратного вызова, соответствующие этим событиям по очереди.
Выполните код в стеке, всегда выполняемый, прежде чем читать «очередь задач». Пожалуйста, смотрите следующий пример.
Кода -копия выглядит следующим образом:
var req = new xmlhttprequest ();
req.open ('Get', url);
req.onload = function () {};
req.onerror = function () {};
req.send ();
Метод REQ.SEND в приведенном выше коде является операцией AJAX по отправке данных на сервер. Это асинхронная задача, которая означает, что система будет читать «очередь задач» только после того, как будет выполнен код в текущем сценарии. Таким образом, это эквивалентно следующему методу написания.
Кода -копия выглядит следующим образом:
var req = new xmlhttprequest ();
req.open ('Get', url);
req.send ();
req.onload = function () {};
req.onerror = function () {};
То есть части указанной функции обратного вызова (Onload и Onerror) не важны до или после метода Send (), поскольку они являются частью стека выполнения, и система всегда будет выполнять их перед чтением «очередь задачи».
5. таймер
В дополнение к размещению асинхронных задач, «очередь задач» также имеет другую функцию, которая заключается в том, чтобы разместить временные события, то есть указывать, как долго будет выполнен определенный код. Это называется функцией «таймер», которая регулярно выполняется код.
Функция таймера в основном выполняется двумя функциями: setTimeout () и setInterval (). Их внутренние механизмы бега точно такие же. Разница в том, что код, указанный первым, выполняется в одно время, в то время как последний выполняется неоднократно. В следующем в основном обсуждаются setTimeout ().
SetTimeout () принимает два параметра, первым является функция обратного вызова, а второе - это количество миллисекундов, чтобы отложить выполнение.
Кода -копия выглядит следующим образом:
console.log (1);
setTimeout (function () {console.log (2);}, 1000);
console.log (3);
Результат выполнения приведенного выше кода составляет 1, 3, 2, потому что SetTimeout () задерживает вторую строку до 1000 миллисекунд.
Если второй параметр setTimeout () установлен на 0, это означает, что указанная функция обратного вызова (0 миллисекундного интервала) выполняется сразу после выполнения текущего кода (стек выполнения).
Кода -копия выглядит следующим образом:
setTimeout (function () {console.log (1);}, 0);
Console.log (2);
Результаты выполнения приведенного выше кода всегда 2 и 1, потому что система выполнит функцию обратного вызова в «очереди задачи» только после выполнения второй строки.
Стандарт HTML5 указывает, что минимальное значение (кратчайший интервал) второго параметра SetTimeout () не должно составлять менее 4 миллисекунд. Если оно ниже этого значения, оно автоматически увеличится. Перед этим старые браузеры устанавливают минимальный интервал до 10 миллисекунд.
Кроме того, для этих изменений DOM (особенно частей, включающих повторное использование страницы), они обычно не выполняются немедленно, но каждые 16 миллисекунд. В настоящее время эффект использования requestAnimationFrame () лучше, чем SetTimeout ().
Следует отметить, что SetTimeout () просто вставляет событие в «очередь задач». Вы должны подождать, пока текущий код (стек выполнения) не будет выполнен до того, как основной поток выполнит функцию обратного вызова, которую он определит. Если текущий код займет много времени, для ожидания может потребоваться много времени, поэтому нет никакой гарантии, что функция обратного вызова будет выполнена в то время, указанную SetTimeout ().
6. Node.js Clack
Node.js также является однопоточной петлей события, но его работающий механизм отличается от механизма браузера.
Пожалуйста, смотрите диаграмму ниже (автор @busyrich).
Согласно приведенным выше рисунке, работающий механизм node.js выглядит следующим образом.
(1) Подготовка двигателя V8 сценариев JavaScript.
(2) Проанализированный код вызывает API узла.
(3) Библиотека LIBUV отвечает за выполнение API узла. Он назначает разные задачи в разные потоки, образует цикл событий и возвращает результаты выполнения задачи в двигатель V8 асинхронным образом.
(4) Двигатель V8 возвращает результат пользователю.
В дополнение к двум методам Settimeout и SetInterval, Node.js также предоставляет два других метода, связанных с «очередью задач»: process.nexttick и setimmediate. Они могут помочь нам углубить наше понимание «очередей задач».
Метод процесса. То есть задачи, которые они определяют, всегда возникают перед всеми асинхронными задачами. Метод SetImmediate запускает функцию обратного вызова на хвосте текущей «очередь задач», то есть задача, которую он определяет, всегда выполняется в следующий раз, когда основной поток считывает «очередь задач», которая очень похожа на SetTimeout (FN, 0). Пожалуйста, смотрите следующий пример (через Stackoverflow).
Кода -копия выглядит следующим образом:
process.nexttick (function a () {
console.log (1);
process.nexttick (function b () {console.log (2);});
});
setTimeout (функция timeout () {
console.log ('Timeout уволен');
}, 0)
// 1
// 2
// время -аут
В приведенном выше коде, поскольку функция обратного вызова, указанная методом Process.nexttick, всегда запускается на хвосте текущего «стека выполнения», не только функция A выполняется сначала, чем тайм -аут функции обратного вызова, указанную с помощью SetTimeout, но функция B также выполняется первой, чем тайм -аут. Это означает, что если есть несколько операторов процесса.
Теперь давайте посмотрим на Setimmediate.
Кода -копия выглядит следующим образом:
setimmediate (функция a () {
console.log (1);
setImmediate (function b () {console.log (2);});
});
setTimeout (функция timeout () {
console.log ('Timeout уволен');
}, 0)
// 1
// время -аут
// 2
В приведенном выше коде есть два Setimmedates. Первый SetImmediate указывает, что функция обратного вызовов A запускается на хвосте текущей «очереди задач» (следующая «петля события»); Затем, SetTimeout также указывает, что тайм -аут функции обратного вызовов запускается на хвосте текущей «очереди задач», поэтому в результате вывода выпущенное время ожидания оценивается позади 1. Что касается 2 -го ранга после выпуска таймаута, это потому, что другая важная особенность SetImmediate: «петля события» может вызвать только функцию обратного вызовов, указанную SETIMMEDIATIATIATIAIAIIAIIEI.
Мы получили важное отличие от этого: несколько операторов процесса. На самом деле, именно поэтому node.js версия 10.0 добавляет метод Setimmediate. В противном случае рекурсивный призыв к Process.nexttick, подобный следующему, будет бесконечным, и главный поток вообще не будет читать «очередь событий»!
Кода -копия выглядит следующим образом:
process.nexttick (function foo () {
process.nexttick (foo);
});
На самом деле, теперь, если вы напишете рекурсивный процесс.
Кроме того, поскольку функция обратного вызова, указанная в процессе, запускается в этом «цикле событий», а Setimmediate указывает, что он запускается в следующем «цикле событий», очевидно, что первое всегда происходит раньше, чем последнее, а также более эффективно в исполнении (потому что нет необходимости проверять «Доклад задачи»).