Механизм событий цикла событий во внешнем интерфейсе

JavaScript

Добавить Автора

Мы знаем, что js выполняется в одном потоке, так как же с этим справляется асинхронный код js? Например, как выводится следующий код:

console.log(1);
setTimeout(function() {
    console.log(2);
}, 0);
new Promise(function(resolve) {
    console.log(3);
    resolve(Date.now());
}).then(function() {
    console.log(4);
});
console.log(5);
setTimeout(function() {
    new Promise(function(resolve) {
        console.log(6);
        resolve(Date.now());
    }).then(function() {
        console.log(7);
    });
}, 0);

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

1. Макрозадачи и микрозадачи

Исходя из нашего многолетнего опыта написания ajax: js следует выполнять в порядке операторов, при асинхронности инициируется асинхронный запрос, за которым следует выполнение, а затем выполняется после возврата асинхронного результата. Но как он справляется с этими исполнительными задачами внутренне?

В js задачи делятся на макрозадачи и микрозадачи, Эти две задачи поддерживают очередь соответственно и выполняются с использованием стратегии «первым пришел — первым вышел»! Все задачи, которые выполняются синхронно, выполняются в задаче макроса.

Макрозадачи в основном включают в себя: сценарий (общий код), setTimeout, setInterval, ввод-вывод, события взаимодействия с пользовательским интерфейсом, postMessage, MessageChannel, setImmediate (среда Node.js).

Основные микрозадачи: Promise.then, MutationObserver, process.nextTick (окружение Node.js).

Конкретные этапы операции следующие:

  1. Взять выполнение задачи из головы задачи макроса;
  2. Если во время выполнения встретится микрозадача, она будет добавлена ​​в очередь микрозадач;
  3. После выполнения макрозадачи, есть ли задачи в очереди микрозадач, если есть, выйти и выполнять их одну за другой до завершения выполнения;
  4. рендеринг графического интерфейса;
  5. Возвращайтесь к шагу 1, пока задача макроса не будет выполнена;

Эти 4 шага составляют механизм обнаружения циклов событий, который мы называемeventloop.

Вернемся к коду, который мы сказали выше:

console.log(1);
setTimeout(function() {
    console.log(2);
}, 0);
new Promise(function(resolve) {
    console.log(3);
    resolve(Date.now());
}).then(function() {
    console.log(4);
});
console.log(5);
setTimeout(function() {
    new Promise(function(resolve) {
        console.log(6);
        resolve(Date.now());
    }).then(function() {
        console.log(7);
    });
}, 0);

Этапы выполнения следующие:

  1. Выполнить журнал(1), вывод 1;
  2. При обнаружении setTimeout добавьте журнал кода обратного вызова (2) в задачу макроса и дождитесь выполнения;
  3. Выполните console.log(3) и добавьте log(4) в микрозадачу;
  4. Журнал выполнения (5), вывод 5;
  5. При обнаружении setTimeout добавьте журнал кода обратного вызова (6, 7) в задачу макроса;
  6. После выполнения задачи макрозадачи проверьте, есть ли задача в очереди микрозадач, есть ли журнал микрозадач(4) (добавлен на шаге 3) и выполните вывод 4;
  7. Извлеките выполнение следующего макроса (2), вывод 2;
  8. После выполнения задачи макрозадачи проверить, есть ли задача в очереди микрозадач, и ее нет;
  9. Отключить выполнение следующей макрозадачи, выполнить log(6) и затем добавить log(7) в микрозадачу;
  10. После выполнения макрозадачи создается журнал микрозадач (7) (добавлен на шаге 9) и результат выполнения 7;

Таким образом, окончательный порядок вывода: 1, 3, 5, 4, 2, 6, 7;

Мы реализуем немного трудоемкую операцию в Promise.then, и этот шаг будет выглядеть более очевидным:

console.log(1);
var start = Date.now();
setTimeout(function() {
    console.log(2);
}, 0);
setTimeout(function() {
    console.log(4, Date.now() - start);
}, 400);
Promise.resolve().then(function() {
    var sum = function(a, b) {
        return Number(a) + Number(b);
    }
    var res = [];
    for(var i=0; i<5000000; i++) {
        var a = Math.floor(Math.random()*100);
        var b = Math.floor(Math.random()*200);
        res.push(sum(a, b));
    }
    res = res.sort();
    console.log(3);
})

В Promise.then сначала генерируется массив из 5 миллионов случайных чисел, а затем массив сортируется. Запуск этого кода показывает, что: 1 будет выведено немедленно, 3 будет выведено через некоторое время, а затем будет выведено 2. Независимо от того, как долго вы ждете вывода 3, 2 всегда будет выводиться после 3. Это также подтверждает, что третий шаг в операции цикла обработки событий должен дождаться выполнения всех микрозадач перед запуском следующей макрозадачи.

Между тем, вывод этого кода интересен:

setTimeout(function() {
    console.log(4, Date.now() - start); // 4, 1380 电脑状态的不同,输出的时间差也不一样
}, 400);

Первоначально он был настроен на вывод через 400 мс, но поскольку предыдущая задача занимала много времени, последующие задачи можно было только отложить и переместить на задний план. Также можно показать, что задержка таких операций, как setTimeout и setInterval, неточна.Эти два метода можно использовать только для задач макроса после 400 мс задачи, но конкретное время выполнения зависит от того, простаивает ли поток.Если в предыдущей задаче есть трудоемкая операция или добавляется бесконечное количество микрозадач, выполнение следующей задачи будет заблокировано..

2. async-await

Из приведенного выше кода также видно, что код в Promise.then принадлежит микросервисам, так как же выполнить код async-await? Например следующий код:

function A() {
    return Promise.resolve(Date.now());
}
async function B() {
    console.log(Math.random());
    let now = await A();
    console.log(now);
}
console.log(1);
B();
console.log(2);

По сути, async-await — это просто синтаксический сахар для Promise+generator. Мы перепишем приведенный выше код, чтобы сделать его более понятным:

function B() {
    console.log(Math.random());
    A().then(function(now) {
        console.log(now);
    })
}
console.log(1);
B();
console.log(2);

Таким образом, мы можем понять порядок вывода: 1, 0,4793526730678652 (случайное число), 2, 1557830834679 (отметка времени);

3. requestAnimationFrame

requestAnimationFrame также является методом, который выполняется асинхронно, но этот метод не является ни макрозадачей, ни микрозадачей. согласно сMDNОпределение в:

window.requestAnimationFrame()Сообщите браузеру, что вы хотите выполнить анимацию, и попросите браузер вызвать указанную функцию обратного вызова, чтобы обновить анимацию перед следующей перерисовкой. Этот метод должен передать функцию обратного вызова в качестве параметра, функция обратного вызова будет выполнена до следующей перерисовки браузера.

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

4. Резюме

Мы должны помнить два самых важных момента: js — это однопоточный механизм и цикл обработки событий.