Изучая JavaScript, я узнал, что JavaScript является однопоточным. Сначала я был озадачен: как один поток обрабатывает трудоемкие операции, такие как сетевые запросы, чтение и запись файлов? Не будет ли эффективность очень низкой? С пониманием и глубоким пониманием этого содержания я познаю тайну. В этой статье в основном объясняется, как JavaScript обрабатывает асинхронные проблемы.
1. Синхронный и асинхронный
Прежде чем представить асинхронный механизм JavaScript, давайте сначала представим: что такое синхронизация? Что такое асинхронность?
Синхронизировать
Если вызывающая сторона может получить ожидаемый результат (то есть получить ожидаемое возвращаемое значение или увидеть ожидаемый эффект) при возврате функции, то функция является синхронной.
Следующим образом:
//在函数返回时,获得了预期值,即2的平方根
Math.sqrt(2);
//在函数返回时,获得了预期的效果,即在控制台上打印了'hello'
console.log('hello');
Вышеуказанные две функции синхронизированы.
Если функция синхронная, она будет ждать, пока не будет получен ожидаемый результат, даже если задача, вызывающая функцию, требует много времени.
асинхронный
Если вызывающая сторона не может получить ожидаемый результат при возврате функции, но должна получить его определенными средствами в будущем, то функция является асинхронной.
Следующим образом:
//读取文件
fs.readFile('hello.txt', 'utf8', function(err, data) {
console.log(data);
});
//网络请求
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xxx; // 添加回调函数
xhr.open('GET', url);
xhr.send(); // 发起函数
Функция чтения файла в приведенном выше примереreadFile
и инициирующая функция сетевого запросаsend
Все будут выполнять трудоёмкие операции.Хотя функция и вернётся немедленно, ожидаемые результаты не могут быть получены сразу, так как трудоемкие операции передаются на выполнение другим потокам, и ожидаемые результаты не могут быть получены временно (описано далее) . А в JavaScript через функцию обратного вызоваfunction(err, data) { console.log(data); }
а такжеonreadystatechange
, передать соответствующую информацию о результате функции обратного вызова после завершения трудоемкой операции и уведомить поток, выполняющий код JavaScript, для выполнения обратного вызова.
Если функция асинхронная, она возвращается сразу после выполнения вызова, но не сразу возвращает ожидаемый результат. Вызывающей стороне не нужно активно ждать. Когда вызываемая сторона получит результат, она будет активно уведомлять вызывающую сторону через функцию обратного вызова.
2. Одно нить и многопоточь
В процессе введения асинхронных процессов может быть замечательным: поскольку JavaScript является одним потоком, все еще есть асинхронные, эти своевременные операции делают?JavaScript на самом деле является языком, однопоточным или многопоточным, зависит от конкретной операционной среды. Запуск JS обычно выполняется в браузере, который анализируется и запускается движком JS. Рассмотрим подробнее браузеры.
браузер
Самые популярные браузеры: Chrome, IE, Safari, FireFox, Opera. Ядро браузера является многопоточным.
Браузер обычно состоит из следующих резидентных потоков:
- Поток механизма рендеринга: как следует из названия, этот поток отвечает за рендеринг страницы.
- Поток движка JS: отвечает за синтаксический анализ и выполнение JS.
- Поток запуска синхронизации: обработка событий синхронизации, таких как setTimeout, setInterval
- Поток, запускающий событие: обработка событий DOM
- Поток асинхронных HTTP-запросов: обрабатывает HTTP-запросы.
Следует отметить, что поток рендеринга и поток JS-движка не могут выполняться одновременно. Когда поток рендеринга выполняет задачи, поток движка JS будет приостановлен. Поскольку JS может манипулировать DOM, если JS обрабатывает DOM во время рендеринга, браузер может быть перегружен.
JS-движок
Обычно, когда мы говорим о браузерах, мы говорим о двух движках: движке рендеринга и движке JS. Механизм рендеринга — это то, как отображать страницу.Chrome/Safari/Opera использует движок Webkit, IE использует движок Trident, а FireFox использует движок Gecko. Различные движки реализуют непоследовательную реализацию одного и того же стиля, что приводит к проблемам совместимости стилей браузеров, которые часто критикуются. Мы не обсуждаем здесь подробно.
Можно сказать, что JS-движок — это виртуальная машина JS, которая отвечает за синтаксический анализ и выполнение кода JS. Обычно включает следующие этапы:
- Лексический анализ: разбивка исходного кода на осмысленные токенизации
- Разбор: разобрать сегментацию слов в синтаксическое дерево с помощью синтаксического анализатора.
- Генерация кода: Генерация кода, который машина может запустить.
- выполнение кода
JS-движки разных браузеров также различаются: Chrome использует V8, FireFox использует SpiderMonkey, Safari использует JavaScriptCore, а IE использует Chakra.
Причина, по которой JavaScript является однопоточным, заключается в том, что браузер открывает только один поток движка JS во время выполнения для анализа и выполнения JS. Тогда почему только один двигатель? Если есть два потока, работающих с DOM одновременно, не будет ли браузер снова перегружен?
Таким образом, хотя JavaScript является однопоточным, браузеры не являются однопоточными внутри. Некоторые операции ввода-вывода, синхронизация таймера и мониторинг событий (щелчок, нажатие клавиши...) выполняются другими потоками, предоставляемыми браузером.
3. Очередь сообщений и цикл событий
Благодаря вышеприведенному пониманию вы можете знать, что JavaScript также реализует асинхронность посредством взаимодействия и сотрудничества потока движка JS и других потоков в браузере. Но когда функция обратного вызова добавляется в поток JS-движка для выполнения? Какой порядок исполнения?
Объяснение всего этого требует постоянного понимания очередей сообщений и циклов событий.
Как показано на рисунке выше, в стеке слева хранятся синхронные задачи, то есть те задачи, которые могут быть выполнены немедленно и не отнимают много времени, например, инициализация переменных и функций, привязка событий и т. д. Те операции, которые не требуют функций обратного вызова, могут быть классифицированы как этот вид.Куча справа используется для хранения объявленных переменных и объектов. Очередь ниже — это очередь сообщений, и как только асинхронная задача получит ответ, она будет помещена в очередь. Например, событие щелчка пользователя, ответ, полученный браузером от службы, и событие, которое должно быть выполнено в setTimeout, каждая асинхронная задача связана с функцией обратного вызова.
Поток движка JS используется для выполнения задач синхронизации в стеке.Когда все задачи синхронизации выполнены, стек очищается, а затем считывается ожидающая задача в очереди сообщений, и соответствующая функция обратного вызова помещается в стек. , и один поток начнет выполняться. Новая задача синхронизации.
Поток движка JS считывает задачи из очереди сообщений в непрерывном цикле. Каждый раз, когда стек очищается, в очередь сообщений считываются новые задачи. Если новых задач нет, он будет ждать, пока появятся новые задачи. Это называется цикл событий.
Вышеуказанная фигура принимает асинхронный запрос AJAX в качестве примера. После инициирована асинхронная задача, нить AJAX выполняет некоторую асинхронную операцию, в то время как нить двигателя JS продолжает выполнять другие синхронные задачи в куче, пока все асинхронные задачи в кучу до всех асинхронных задач Куча выполнена. Затем сообщения последовательно выделяются из очереди сообщений в качестве синхронной задачи и выполняются в потоке двигателя JS, то функция обратного вызова AJAX будет вызываться и выполнена в определенное время.4. Примеры
Обратитесь к вопросам интервью, которые исследуют асинхронный механизм JavaScript, упомянутый в статье для конкретного введения.
Выполните следующий код. После выполнения нажмите дважды в течение 5 секунд. Через некоторое время (> 5 секунд) нажмите еще раз. Каков результат всего процесса?
setTimeout(function(){
for(var i = 0; i < 100000000; i++){}
console.log('timer a');
}, 0)
for(var j = 0; j < 5; j++){
console.log(j);
}
setTimeout(function(){
console.log('timer b');
}, 0)
function waitFiveSeconds(){
var now = (new Date()).getTime();
while(((new Date()).getTime() - now) < 5000){}
console.log('finished waiting');
}
document.addEventListener('click', function(){
console.log('click');
})
console.log('click begin');
waitFiveSeconds();
Чтобы понять вывод приведенного выше кода, сначала представим таймер.
setTimeout
Функция состоит в том, чтобы вставить функцию обратного вызова в очередь сообщений через определенный интервал и выполнить ее после выполнения задач синхронизации в стеке. Поскольку задачи синхронизации в стеке также требуют времени,Следовательно, время интервала обычно больше или равно указанному времени..
setTimeout(fn, 0)
Это означает, что функция обратного вызова fn немедленно вставляется в очередь сообщений, ожидая выполнения, а не выполняется немедленно. См. пример:
setTimeout(function() {
console.log("a")
}, 0)
for(let i=0; i<10000; i++) {}
console.log("b")
b a
Результат печати показывает, что функция обратного вызова не выполняется немедленно, а выполняется после выполнения задач в стеке. Пока задача в стеке выполняется, она должна ждать.
После понимания функции таймера легко получить результат вывода.
Во-первых, сначала выполните задачу синхронизации. вwaitFiveSeconds
Это трудоемкая операция, которая длится до 5 секунд.
0
1
2
3
4
click begin
finished waiting
Затем, когда выполняется поток JS-движка, обратный вызов, сгенерированный таймером, соответствующим «таймеру a», обратный вызов, сгенерированный таймером, соответствующим «таймеру b», и обратный вызов, соответствующий двум кликам, последовательно помещаются в очередь сообщений. . Поскольку поток движка JS простаивает, онСначала проверьте, есть ли событие для выполнения, а затем обработать другие асинхронные задачи. Следовательно, получается следующая выходная последовательность.
click
click
timer a
timer b
Наконец, два события щелчка через 5 секунд помещаются в очередь сообщений, и, поскольку в это время поток JS-движка простаивает, они выполняются немедленно.
click
click
Справочная статья
JavaScript: глубокое понимание синхронизации, асинхронности и цикла событий.
сказать модель цикла событий из setTimeout
Однопоточный JavaScript и асинхронный механизм
Однопоточный механизм JavaScript
За однопоточной асинхронностью в JavaScript — механизм цикла событий
Подробное объяснение механизма работы JavaScript: снова поговорим о цикле событий