Введение
Когда я искал функцию дросселирования, я наткнулся на setTimtout, поэтому я копался в цикле событий из механизма запуска js. Итак, давайте начнем с этой простой функции газа.
// 节流:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
function throttle (fn, delay) {
let sign = true;
return function () { // 闭包,保存变量的值,防止每次执行次函数,值都被重置
if (sign) {
sign = false;
setTimeout (() => {
fn();
sign = true;
}, delay);
} else {
return false;
}
}
}
window.onscroll = throttle(foo, 1000);
Так как же эта функция дросселирования обеспечивает дросселирование?
Давайте посмотрим на шаги его выполнения (при условии, что мы прокручивали):
- Когда мы открываем страницу, код выполняется для
window.onscroll = throttle(foo, 1000)
Функция дросселя будет выполняться напрямую, определяя переменнуюsign
истинно, затем нажмите клавишу возврата, чтобы выйти из функции дросселя и вернуть другую анонимную функцию. - Затем мы прокручиваем страницу, после чего срабатывает событие onscroll и выполняется функция дросселя. В настоящее время наша функция дросселя на самом деле является анонимной функцией, которая выполняет возврат. Из-за замыкания значение знака сохраняется (такое ощущение, что я должен заполнить дыру для замыкания...), и знак в это время истинен. Просто выполните суждение if и измените знак на false. Затем мы нажимаем на таймер, и теперь нас не волнует содержимое функции обратного вызова таймера.
- Мы все еще прокручиваем, затем снова запускается событие onscroll, поэтому решение if else продолжается. В этот момент знак уже ложный и ничего не происходит.
- Продолжайте, мы непрерывно прокручиваем, а событие onscroll все еще запускается, потому что знак все еще ложный, поэтому ничего не происходит.
- Повторяйте шаг 4 до тех пор, пока через 1 с не будет выполнено событие onscroll, не будет выполнен наш setTimeout, сначала выполните нашу функцию fn(), которую необходимо выполнить, а затем установите для знака значение true. Как и прежде, выполняется решение if.
Так почему же мы столкнулись с setTimeout в процессе выполнения решения if, и наш знак не изменился на true, чтобы всегда выполнялось решение if? Затем нам нужно поговорить о механизме работы js. Наконец-то добрались до сути, это не просто...
механизм запуска js
Давайте посмотрим на босса Жуань Ифэн.
(1) Все задачи синхронизации выполняются в основном потоке, формируястек выполнения(стек контекста выполнения).
(2) В дополнение к основному потоку существует еще «очередь задач» (task queue). Как только асинхронная задача имеет запущенный результат, событие помещается в «очередь задач».
(3) Как только все задачи синхронизации в «стеке выполнения» будут выполнены, система прочитает «очередь задач», чтобы увидеть, какие события в ней находятся. Затем соответствующие асинхронные задачи завершают состояние ожидания, входят в стек выполнения и начинают выполнение.
(4) Основной поток продолжает повторять третий шаг выше.
Моя собственная классификация заключается в том, что js имеет:
-
Синхронные и асинхронные задачи
-
Макрозадачи и микрозадачи
-
Основной поток (синхронизированные задачи) — все задачи синхронизации выполняются в основном потоке, образуя стек выполнения.
-
Очередь задач (асинхронная задача): когда асинхронная задача имеет результат, событие помещается в очередь задач.
-
Механизм работы JS: когда «стек выполнения» ввсеПосле выполнения задачи синхронизации система прочитает «очередь задач».
Задачи макроса включают в себя: сценарий (основной код), setTimeout, setInterval, setImmediate, ввод-вывод, рендеринг пользовательского интерфейса.
К микрозадачам относятся: process.nextTick (Nodejs), Promises, Object.observe, MutationObserver.
Здесь мы замечаем, что в задаче макроса есть скрипт, который является нашим основным кодом для нормального выполнения.
цикл событий цикл событий
Основной поток считывает события из «очереди задач», и этот процесс цикличен, поэтому весь механизм работы также называется Event Loop. Этот механизм заключается в следующем: основной поток будет непрерывно извлекать задачи из очереди задач для последовательного выполнения, и каждый раз при выполнении задачи он будет проверять, не пуста ли очередь микрозадач (специфическим признаком выполнения задачи является то, что стек выполнения функции пуст), если нет Если он пуст, то все микрозадачи будут выполняться одновременно. Затем введите следующий цикл, чтобы взять следующую задачу из очереди задач для выполнения.
Я дал краткое изложение общего процесса: скрипт (макрозадача) - очистить очередь микрозадачи - выполнить макрозадачу - очистить очередь микрозадачи - выполнить макрозадачу и так далее.
- Сначала выполните код синхронизации в скрипте (это задача макроса). При обнаружении асинхронной задачи поместите ее в очередь задач.
- Узнать, есть ли микрозадачи в очереди задач, и выполнить все микрозадачи в это время по порядку (именно поэтому promise выполняется раньше setTimeout, потому что макрозадача, выполняемая первой, является синхронным кодом, setTimeout ставится в очередь задач, setTimeout Это снова макрозадача, а перед ней должна быть выполнена микрозадача (например, обещание).
- Выполните задачу макроса (тот, что стоит в очереди первым), а затем поместите задачи макроса и микрозадачи в этой задаче макроса в очередь задач.
- ... продолжайте повторять шаги 2 и 3
Чтобы иметь в виду очередь, используйте концепцию «первым пришел — первым ушел».
Позаимствуйте фотографию у официантки, чтобы объяснить:
Теперь посмотрим на функцию дросселирования в начале и поймем, почему мы столкнулись с setTimeout, наш знак не изменился на true.
Тогда давайте продолжим и рассмотрим самые популярные вопросы, которые мы видели в последнее время.
начать прорываться
первый раунд
посмотрите на этот код
console.log('script start');
setTimeout(() => {
console.log('setTimeout1');
}, 0);
new Promise((resolve) => {
resolve('Promise1');
}).then((data) => {
console.log(data);
});
new Promise((resolve) => {
resolve('Promise2');
}).then((data) => {
console.log(data);
});
console.log('script end');
Нетрудно сделать выводы из описанного выше процесса реализации.script start -> script end -> Promise1 -> Promise2 -> setTimeout1
Даже если setTimeout не задерживает выполнение, он будет выполнен после Promise.Кто сказал js сначала выполнить синхронный код, затем найти микрозадачи, а затем макрозадачи.
Теперь, когда мы это поняли, давайте двигаться дальше.
Второй проход
setTimeout(() => {
console.log('setTimeout1');
setTimeout(() => {
console.log('setTimeout3');
}, 0);
Promise.resolve().then(data=>{
console.log('setTimeout 里的 Promise');
});
}, 0);
setTimeout(() => {
console.log('setTimeout2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise1');
});
По предыдущему процессу
- Выполните скрипт и увидите, что первый setTimeout ставится в очередь задач, а второй setTimeout ставится в очередь задач. Я видел, что Promise.then() ставится в очередь задач, а кода синхронизации нет.
- Проверил микрозадачу и обнаружил, что Promise.then() печатает
Promise1
. - Проверяем и обнаруживаем, что других микрозадач нет Проверяем макрозадачу В это время макрозадач две (два setTimeouts), но правила говорят нам, что выполняется только одна макрозадача, т.к. является принципом «первым поступил — первым вышел», и выполнение сначала входит в очередь.
setTimeout1
. Другой setTimeout был найден и помещен в очередь задач. Увидев Promise.then() , печатаетsetTimeout 里的 Promise
. - Проверьте задачу макроса, найдите задачу макроса, выполните расширенную задачу, поэтому напечатайте
setTimeout2
. - Проверьте микрозадачи, нет.
- проверить задачу макроса, распечатать
setTimeout3
.
После того, как мы это выясним, мы будем продолжать играть?
Третий уровень
console.log('script start');
setTimeout(() => {
console.log('setTimeout1');
}, 0);
new Promise((resolve) => {
console.log('Promise3');
resolve();
}).then(() => {
console.log('Promise1');
});
new Promise((resolve) => {
resolve();
}).then(() => {
console.log('Promise2');
});
console.log('script end');
Давайте посмотрим на результат выполнения этого кода.
script start -> Promise3 -> script end -> Promise1 -> Promise2 -> setTimeout1
Некоторые друзья могут сказать, дело не в том, что Promise — это микрозадача, ее нужно выполнить после выполнения основного кода, зачем вы предали Promise3.
На самом деле, Promise 3 не пострадал.Упомянутая ранее микрозадача Promise — это код, выполняемый .then(). Код в функции обратного вызова нового Promise является синхронной задачей.
Четвертый проход
Продолжим смотреть на обещания
setTimeout(()=>{
console.log(1)
},0);
let a=new Promise((resolve)=>{
console.log(2)
resolve()
}).then(()=>{
console.log(3)
}).then(()=>{
console.log(4)
});
console.log(5);
Это выводит 2 -> 5 -> 3 -> 4 -> 1. Вы правы?
С точки зрения реализации Promise исполнитель Promise — это синхронная функция, то есть функция, которая не является асинхронной и выполняется немедленно, поэтому она должна выполняться вместе с текущей задачей. Затем цепочный вызов Promise каждый раз внутри генерирует новый Promise, а затем выполняет его. макрозадач будет выполняться только после его очистки.
Уровень 5
Обещание продолжает развиваться
new Promise((resolve,reject)=>{
console.log("promise1")
resolve()
}).then(()=>{
console.log("then11")
new Promise((resolve,reject)=>{
console.log("promise2")
resolve()
}).then(()=>{
console.log("then21")
}).then(()=>{
console.log("then23")
})
}).then(()=>{
console.log("then12")
})
Давайте просто объясним.
В случае с этим вложенным промисом не паникуйте, во-первых, у вас в сердце очередь, вы можете поставить эти функции в соответствующую очередь.
Ready GO
первый раунд
- текущая задача: promise1 — это функция, которая заслуживает немедленного выполнения. Обратитесь к исполнителю в предыдущей главе, чтобы немедленно выполнить вывод
[promise1]
- очередь микрозадач: [первая, затем promise1]
второй раунд
- текущая задача: выполняется then1 и выводится немедленно
then11
и новое обещание2promise2
- очередь микрозадач: [затем функция нового обещания2, а вторая затем функция обещания1]
третий раунд
- текущая задача: тогда вывод функции нового обещания2
then21
а второй затем вывод функции promise1then12
.- очередь микрозадач: [вторая функция нового обещания2]
четвертый раунд
- текущая задача: вывод второй, затем функция нового обещания2
then23
- micro task queue: []
END
У некоторых людей могут возникнуть вопросы по поводу второго раунда очереди, почему сначала в очередь входит функция "тогда нового обещания2", а потом "тогда функция второго затем обещания1"? "Вторая потом функция нового promise2" Почему не встала в очередь в этом раунде?
Неважно, если вы не понимаете, давайте отладим код:
после печатиpromise2
После этого строка 19 выполняется первой})
здесь, а потом здесь.
Следующий шаг ко второму обещанию1})
Вот. Журнал console.log строки 20 не выполняется.
Видно, что первый из promise2 попал в очередь задач. .then() не выполняется.
продолжить, распечататьthen21
.
Отсюда следует, что второй then из promise1 помещается в асинхронную очередь и не выполняется. Когда здесь выполняется программа, выполняется задача макроса. Проверьте микрозадачу, в это время в очереди есть ['then function of new promise2', 'the second then function of promise1'], то есть очередь, написанная во втором раунде.
На этом этапе перед двумя затем обещания2})
.
Выполнен до этого момента, снова столкнулся с асинхронностью и поместил его в очередь.
Очередь в этот момент: ['вторая, затем функция обещания1', 'вторая, затем функция обещания2']
печатать обещания1then12
.
Первый пришел, первый ушел, так что сначала выполняется «второе обещание 1, затем функция».
Очередь в этот момент: [ 'Promise2's second then function' ]
наконец выводthen23
.
Шестое выключение async/await
К последнему уровню я думал, что полностью понял цикл событий. Позже я увидел async/await, async await — этоgenerator
а такжеPromise
Каждый должен знать синтаксический сахар , но после того, как я его распечатал, это было не то, что я ожидал, и это немного сбивало с толку.
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end');
Этот код тоже чистый красный код, я видел его не менее чем в трех местах...
Тщательно подумайте, что должно быть выведено, а затем распечатайте, чтобы увидеть. (версия Chrome 73 печатает результат)
script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout
Просто начните с асинхронности.
Когда программа выполняетсяasync1();
когда
-
выводить первым
async1 start
-
выполнить до
await async2();
, будет выполняться справа налево, выполняться первымasync2()
,Распечататьasync2
,видетьawait
, заблокирует код для выполнения синхронной задачи.
async/await влияет только на выполнение внутри функции, а не на порядок выполнения вне функции. Другими словами, async1() не блокирует выполнение последующих программ.
await async2()
Эквивалент Обещания,console.log("async1 end");
Эквивалент функции, выполняемой после then предыдущего промиса.
Таким образом, можно получить вышеуказанный результат.
Однако вы можете напечатать что-то вроде этого:
Это связано с V8 (в версии chrome 71 я распечатываю результат на картинке). Что касается того, кто будет выполнять async/await и обещание первым, давайте здесь поленимся, посмотримXiaomena: EventLoop не страшнее, ужасно состоит в том, чтобы встретить обещаниеВерсия 4 здесь имеет очень подробную интерпретацию.
Седьмой уровень: Node: process и setImmediate (версия после node11)
Сначала посмотрите на первый код и подумайте над ответом
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
});
async1()
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
setImmediate(()=>{
console.log("setImmediate")
})
process.nextTick(()=>{
console.log("process")
})
console.log('script end');
Посмотрите на код ниже и подумайте над ответом, будет ли он другим?
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
}, 1000)
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
setImmediate(() => {
console.log('setImmediate')
})
process.nextTick(() => {
console.log('process')
})
console.log('script end')
Сначала посмотрите ответ: Первый
script start
async1 start
async2
promise1
script end
process
async1 end
promise2
setTimeout
setImmediate
второй
script start
async1 start
async2
promise1
script end
process
async1 end
promise2
setImmediate
setTimeout
Алле? Порядок setTimeout и setImmediate на самом деле другой. почему это
7.1 setImmediate
Потому что setImmediate выполняется только немедленно в обратном вызове ввода-вывода.
Для приведенного выше кода setTimeout может выполняться до или после. Первый setTimeout(fn, 0) === setTimeout(fn, 1), который определяется исходным кодом Вход в цикл событий тоже стоит денег, если на подготовку уходит больше 1 мс, callback setTimeout будет выполняться прямо в фазе таймера. Если время подготовки занимает менее 1 мс, то сначала выполняется обратный вызов setImmediate
Я лично проверял, когда setTimeout(fn, 2), setTimeout также выполняется первым, если он установлен на 3 мс или больше, setImmediate будет выполняться первым.
Но когда оба вызываются внутри асинхронного обратного вызова ввода-вывода, setImmediate всегда выполняется первым, а затем setTimeout.
const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
// immediate
// timeout
7.2 process.nextTick
Эта функция на самом деле не зависит от Event Loop, у нее есть своя очередь, и по завершении каждого этапа, если есть очередь nextTick, она очистит все callback-функции в очереди и выполнит ее раньше других микрозадач.
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
process.nextTick(() => {
console.log('nextTick')
})
})
})
})
// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
Справочная статья:
Анж: Говоря о js, анти-тряске и троттлинге
Ruan Yifeng: Подробное объяснение механизма выполнения JavaScript: снова поговорим о цикле событий
Miss Front-end: Тщательно разберитесь в цикле событий браузера
Сяомей Нана: Eventloop — это не страшно, страшно встретить Promise
Long Jincen: механизм цикла событий js (цикл событий на стороне браузера) и понимание async/await
Плавание по волнам: в чем разница между браузером и циклом событий Node?