理解 JavaScript 事件循环机制

252 阅读2分钟

前言

浏览器执行环境的核心思想基于:同一时刻只能执行一个代码片 段,即所谓的单线程执行模型。想象一下在银行柜台前排队,每个人进 入一支队伍等待叫号并“处理”。但JavaScript则只开启了一个营业柜台! 每当轮到某个顾客时(某个事件),只能处理该位顾客。

来看下面这段 JavaScript 代码:

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

先猜测一下这段代码的输出顺序是什么。

事件循环和任务队列

所有的任务可以分为同步任务和异步任务, 分别进入不同的执行环境。 同步任务,一般会直接进入到主线程中,立即执行;而异步任务,就是异步执行的任务(比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列( Event Queue )的机制来进行协调。

当主线程内的任务执行完毕,会去 任务队列(Event Queue) 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 事件循环(Event Loop)。

事件循环(Event Loop)通常至少需要两个任务队列:宏任务队列和微任务队列。两种队列在同一时 刻都只执行一个任务。 p1.png

代码分析

如上所说, 让我们来看看上段代码的执行过程: 1.整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start 2.遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中 3.遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,将其分到微任务 Event Queue 中,记为 then2 4.遇到 console.log,输出 script end

至此,Event Queue 中存在三个任务: p2.png

1.执行微任务:执行then1,输出 promise1, 2.执行微任务:执行 then2,输出 promise2, 这样就清空了所有微任务 3.执行宏任务: 输出 setTimeout 至此,输出的顺序是: script start, script end, promise1, promise2, setTimeout

附上一题,你可以根据上述方法来做个练习:

console.log('script start');

setTimeout(function() {
  console.log('timeout1');
}, 10);

new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})

console.log('script end');

总结

记住,JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的。

参考文献

Tasks, microtasks, queues and schedules

JavaScript忍者秘籍第二版 第十三章 弥久历新的事件