事件循环
我们知道,JavaScript只支持单线程任务,即同一时间内只能执行一项任务。因此JS的底层巧妙地设计了一个执行栈,将事件执行顺序形成一个任务队列。
事件循环步骤如下:
- 所有同步任务都在主线程执行,形成一个执行栈
- 主线程之外有一个消息队列,只要异步操作执行完成(就是进入了回调),就到消息队列排队等候回调
- 同步代码,即栈中代码执行完后,就到消息队列中读取异步任务,结束异步人物的等待状态,并且放到执行栈中(并不是执行在队列执行),开始执行。
- 不停重复上面三步。
来看看最简单的例子:
console.log("第一项任务");
console.log("第二项任务");
console.log("第三项任务");
以上代码,JS将会按照顺序依次添加到任务队列(先进先出),在依次执行。
但如果我们修改一下?
console.log("执行整体环境")
setTimeout(() => {
console.log("执行setTime")
},0)
new Promise((resolve) => {
resolve()
}).then(function () {
console.log("执行promise")
})
console.log("执行整体环境结束")
像上面的代码就不会像预期一样从头到尾直接执行出来:
执行整体环境
执行整体环境结束
执行promise
执行settime
那究竟是为啥呢?(黑人问号?)这就牵扯到下面的内容了。
宏任务与微任务
- 宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
- 微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)。
- 为什么要这么区分?原因是他们的执行顺序不同。
- 开始按照顺序,执行整体环境,算执行一个宏任务,把整体环境的代码调入执行栈,开始执行整体环境的代码。当遇到微任务注册的时候,把微任务进入微任务队列,遇到宏任务的时候进入到宏任务队列。
- 当执行完一次宏任务之后,会去查看微任务队列有没有东西,如果有东西,把微任务队列所有任务处理完毕。
- 执行一下个宏任务。
- 按照上面流程,一直进行循环。
也就是说,每当执行一次宏任务,都会去检查微任务队列是否有新的任务,把微任务队列清空才会继续下一次宏任务。
结合更复杂的代码理解:
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');
//执行结果
script start
promise1
script end
then1
timeout1
timeout2