Известно, что JavaScript однопоточный, по своей сути асинхронный, подходит для IO-интенсивного, но не для процессорного, но почему он асинхронный и откуда он берется?Реализации мы постепенно обсудим здесь.
1. Процессы и потоки
1. Браузеры многопроцессорны
В основном это следующие процессы:
- Процесс браузера: основной процесс браузера, единственный, отвечающий за создание и уничтожение других процессов, загрузку и управление сетевыми ресурсами, отображение интерфейса браузера, вперед и назад и т.д.
- Процесс GPU: для 3D-рисования и т. д. до одного.
- Процессы сторонних подключаемых модулей: один процесс для каждого типа подключаемого модуля, создаваемый только при использовании этого подключаемого модуля.
- Процесс рендеринга браузера (ядро браузера): внутри многопоточный.Каждый раз, когда открывается новая веб-страница, создается процесс, который в основном используется для рендеринга страницы, выполнения скриптов, обработки событий и т. д.
2. Процесс рендеринга (ядро браузера)
Процесс рендеринга браузера является многопоточным, в нем выполняются рендеринг страницы, выполнение JavaScript и цикл обработки событий:
- Поток рендеринга графического интерфейса: отвечает за рендеринг интерфейса браузера. Когда интерфейс необходимо перерисовать (Repaint) или вызвать перекомпоновку (Reflow) в результате какой-либо операции, этот поток будет выполнен.
- Поток движка JavaScript: также известный как ядро JavaScript, отвечает за обработку сценариев Javascript, анализ сценариев Javascript, выполнение кода и т. д. (например, двигатель V8)
- Поток триггера события: используется для управления циклом событий браузера. Обратите внимание, что он не управляется потоком движка JavaScript. Когда событие инициируется, поток добавит событие в конец очереди ожидания и будет ждать, пока движок JavaScript не выполнит его. обработать это.
- Временная триггерная нить: легендарная
setIntervalа такжеsetTimeoutВ треде обратите внимание, что W3C указывает в стандарте HTML, что требованияsetTimeoutВременные интервалы менее 4 мс считаются как 4 мс. - Поток асинхронного HTTP-запроса: in
XMLHttpRequestПосле подключения откройте новый запрос потока через браузер.При обнаружении изменения состояния, если установлена функция обратного вызова, асинхронный поток будетГенерация события изменения состояния, снова поместите этот обратный вызов в очередь событий. А затем выполняется движком JavaScript.
Уведомление,Поток рендеринга GUI и поток механизма JavaScript являются взаимоисключающими., при выполнении механизма JavaScript поток графического интерфейса будет приостановлен (эквивалентно замораживанию), а обновления графического интерфейса будут сохранены в очереди.Подождите, пока движок JavaScript не будет простаиватьбыть исполнены немедленно. Поэтому, если время выполнения JavaScript слишком велико, это приведет к непоследовательности рендеринга страницы, что приведет к блокировке рендеринга и загрузки страницы.
2. Однопоточный JavaScript
Так называемый одиночный поток означает, что поток, отвечающий за интерпретацию и выполнение кода JavaScript в движке JavaScript, уникален, и одновременно может выполняться только одна задача.
Вопрос: зачем вводить однопоточный бормотание?
мы знаем:
- Браузер должен отображать DOM
- JavaScript может изменять структуру DOM
- Рендеринг DOM браузера останавливается во время выполнения JavaScript
Если поток движка JavaScript не является однопоточным, одновременно могут выполняться несколько фрагментов JavaScript. Если эти несколько фрагментов JavaScript изменяют DOM, возникает конфликт DOM.
Вы могли бы сказать,web workerМногопоточность поддерживается, но веб-воркеры не могут получить доступ к оконным объектам, объектам документа и т. д.
Причина: избегайте конфликтов рендеринга DOM.
Конечно, мы можем ввести для браузераЗамокМеханизм разрешает эти конфликты, но он сильно увеличивает сложность, поэтому JavaScript выбрал однопоточное выполнение с момента своего рождения.
Введение единого потока означает, что все задачи должны быть поставлены в очередь, а следующая задача будет выполняться после завершения предыдущей задачи. Это также приводит к проблеме: если предыдущая задача занимает много времени, последняя задача должна ждать вечно.
// 实例1
let i, sum = 0
for(i = 0; i < 1000000000; i ++) {
sum += i
}
console.log(sum)
В примере 1sumЕго нельзя распечатать сразу, его нужно выполнить после выполнения цикла for.console.log(sum).
// 实例2
console.log(1)
alert('hello')
console.log(2)
В примере 2 браузер сначала печатает1, а затем появится всплывающее окно, нажмите OK, чтобы выполнитьconsole.log(2).
Суммировать:
- Преимущества: реализация относительно проста, а среда выполнения относительно проста.
- Недостаток: пока одна задача занимает много времени, следующие задачи должны ставиться в очередь, что задержит выполнение всей программы. Обычные браузеры не отвечают (притворная смерть), часто потому, что определенный фрагмент кода Javascript выполняется в течение длительного времени (например, бесконечный цикл), в результате чего вся страница зависает на этом месте, и другие задачи не могут быть выполнены.
Для решения этой проблемы язык JavaScript разделяет режимы выполнения задач на два типа: синхронный и асинхронный.
3. Синхронный и асинхронный
1. Синхронизация
func(args...)
если в функцииfuncПри возврате вызывающая сторона может получить ожидаемый результат (то есть получить ожидаемое возвращаемое значение или увидеть ожидаемый эффект), тогда функция является синхронной.
let a = 1
Math.floor(a)
console.log(a) // 1
2. Асинхронный
если в функцииfuncПри возврате вызывающая сторона не может получить ожидаемый результат, но должна получить его определенными средствами в будущем, тогда эта функция является асинхронной.
fs.readFile('foo.txt', 'utf8', function(err, data) {
console.log(data);
});
Суммировать:
JavaScript использует асинхронное программирование по двум причинам.
- Во-первых, JavaScript однопоточный;
- Во-вторых, улучшить загрузку процессора.
4. Асинхронный процесс
fs.readFile('data.json', 'utf8', function(err, data) {
console.log(data)
})
При выполнении этого кодаfs.readFileКогда функция возвращается, она не будет напечатана сразуdata,Толькоdata.jsonПечатает только после завершения чтения. асинхронная функцияfs.readFileВыполнение выполняется очень быстро, но все еще есть рабочие потоки, которые выполняют асинхронные задачи, уведомляют основной поток и вызывают его обратно.Этот процесс называется асинхронным процессом.
Основной поток инициирует асинхронную операцию, а соответствующий рабочий поток принимает запрос и сообщает основному потоку о том, что он получен (асинхронная функция возвращается), основной поток продолжает выполнять последующие задачи, а рабочий поток выполняет асинхронные задачи; после того, как рабочий поток завершает задачу, он уведомляет основной поток; после того, как основной поток получает уведомление, он выполняет определенные действия (вызывает callback-функцию).
Рабочий поток уведомляет основной поток после завершения асинхронной операции, так как же выглядит этот механизм уведомления? Ответ — очереди сообщений и циклы событий.
Пять, очередь сообщений и цикл событий
Рабочий поток помещает сообщение в очередь сообщений, а основной поток извлекает сообщение через процесс цикла обработки событий.
- Очередь сообщений. Очередь сообщений — это очередь в порядке очереди, в которой хранятся различные сообщения.
- Цикл событий. Цикл событий относится к процессу, в котором основной поток неоднократно извлекает и выполняет сообщения из очереди сообщений.
1. цикл событий
Основной поток непрерывно извлекает сообщения из очереди сообщений и выполняет сообщения.Этот процесс называется циклом событий.Этот механизм называется механизмом цикла событий.Процесс извлечения сообщения и его выполнения называется циклом.
Общий процесс реализации выглядит следующим образом:
while(true) {
var message = queue.get()
execute(message)
}
Например:
$.ajax({
url: 'xxxx',
success: function(result) {
console.log(1)
}
})
setTimeout(function() {
console.log(2)
}, 100)
setTimeout(function() {
console.log(3)
})
console.log(4)
// output:4321 或 4312
Среди них основной поток:
// 主线程
console.log(4)
Асинхронная очередь:
// 异步队列
function () {
console.log(3)
}
function () { // 100ms后
console.log(2)
}
function() { // ajax加载完成之后
console.log(1)
}
Цикл событий — это специфическое решение для реализации асинхронности в JavaScript, при котором синхронный код выполняется напрямую, асинхронная функция сначала помещается в асинхронную очередь, а после выполнения синхронной функции опрашивается callback-функция асинхронной очереди. и казнен.
2. Очередь сообщений
Среди них сообщение — это функция обратного вызова, добавляемая при регистрации асинхронной задачи.
$.ajax('XXX', function(res) {
console.log(res)
})
...
После того, как основной поток инициирует запрос AJAX, он продолжит выполнение другого кода, а за запрос отвечает поток AJAX.XXX, после получения запроса он будет инкапсулирован в объект JavaScript, после чего будет построено сообщение:
// 消息队列里的消息
var message = function () {
callback(response)
}
вcallbackФункция обратного вызова при успешном ответе на сетевой запрос AJAX.
После того, как основной поток выполнит весь код в текущем цикле, он перейдет в очередь сообщений, чтобы получить сообщение (то есть,messageфункцию) и выполнить ее. На данный момент рабочий поток к основному потоку завершен.通知, функция обратного вызова выполняется. Если основной поток не предоставляет функцию обратного вызова в начале, после того, как поток AJAX получит ответ HTTP, нет необходимости уведомлять основной поток и нет необходимости помещать сообщения в очередь сообщений.
Функция обратного вызова в асинхронном процессе не должна выполняться в текущем раунде цикла обработки событий.
6. Асинхронность и события
Каждое сообщение в очереди сообщений фактически соответствует событию.
Одним из важных асинхронных процессов является:DOM-события
var button = document.getElementById('button')
button.addEventListener('click', function(e) {
console.log('事件')
})
С асинхронной точки зрения,addEventListenerФункция является инициирующей функцией асинхронного процесса, а функция прослушивателя событий является функцией обратного вызова асинхронного процесса. Когда событие запускается, это означает, что асинхронная задача завершена, а функция прослушивателя событий инкапсулируется в сообщение и помещается в очередь сообщений, ожидая выполнения основного потока.
Концепция событий на самом деле не нужна, механизм событий на самом деле является механизмом уведомления асинхронного процесса.
Кроме того, все асинхронные процессы также могут быть описаны событиями. Например:
setTimeout(func, 1000)
// 可以看成:
timer.addEventListener('timeout', 1000, func)
Подробное описание мероприятия вы можете прочитать в этой статье:Привязка событий, прослушивание событий, делегирование событий, который не будет здесь подробно рассматриваться.
7. Производители и потребители
Проблема производителя-потребителя — это классическая проблема в потоковой модели: производитель и потребитель совместно используют одно и то же пространство хранения в один и тот же период времени, производитель добавляет данные в пространство хранения, а потребитель берет данные из пространства хранения. место для хранения пусто, потребитель блокируется, а когда место для хранения заполнено, блокируется производитель.
С точки зрения производителей и потребителей асинхронный процесс выглядит так:
Рабочие потоки являются производителями, а основной поток — потребителем (есть только один потребитель). Рабочий поток выполняет асинхронные задачи. После завершения выполнения соответствующая функция обратного вызова инкапсулируется в сообщение и помещается в очередь сообщений, основной поток непрерывно извлекает сообщения из очереди сообщений и выполняет их. Когда очередь сообщений пуста, основной поток блокируется до тех пор, пока очередь сообщений снова не станет пустой.
Итак, каковы методы реализации асинхронности?
- До ES6: обратный вызов, цикл обработки событий, обещание
- ES6: Генератор
- ES7:Async/Await
Восемь, предыдущая расширенная серия JS (постоянно обновляется)
var, let, const, деструктуризация, расширение, новое, это, класс, функция
Углубленный конструктор, прототип,proto, [[Prototype]] и цепочка прототипов
Шесть реализаций наследства JS
Эта статья познакомит вас с оператором instanceof.
Глубокое погружение в вызов, применение, привязку, функции стрелок и каррирование
Хотите увидеть больше статей в серии,Нажмите, чтобы перейти на домашнюю страницу блога github.
Обратите внимание на бутылочного джентльмена, получите уведомление о последней серии статей.
Если вы считаете, что это хорошо, поставьте лайк! 👍👍👍
9. Иди до конца
-
❤️ Получайте удовольствие, продолжайте учиться и всегда продолжайте программировать. 👨💻
-
Если у вас есть какие-либо вопросы или уникальные идеи, добро пожаловать в комментарии или свяжитесь с бутылкой напрямую (отсканируйте код, чтобы подписаться на официальный аккаунт, и ответьте на 123)! 👀👇
-
👇Приглашаем обратить внимание: джентльмен с бутылкой переднего плана, обновляется ежедневно! 👇