前言
浏览器执行环境的核心思想基于:同一时刻只能执行一个代码片 段,即所谓的单线程执行模型。想象一下在银行柜台前排队,每个人进 入一支队伍等待叫号并“处理”。但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)通常至少需要两个任务队列:宏任务队列和微任务队列。两种队列在同一时 刻都只执行一个任务。
代码分析
如上所说, 让我们来看看上段代码的执行过程:
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 中存在三个任务:
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忍者秘籍第二版 第十三章 弥久历新的事件