聊聊Js中的Event Loop事件循环机制在讲Event Loop之前,我们首先要知道Javascript是一门单线程
前言
在讲Event Loop之前,我们首先要知道Javascript是一门单线程语言,简单来说,在同一时间它只能做一件事,做完这件事才能做下一件事。这样虽然有它的好处,但却会带来代码阻塞。而Event Loop能在任务队列和执行栈之间进行协调,帮助我们更好地处理异步任务。
console.log(1)
setTimeout(() => {
console.log(2);
},3000);
console.log(3);
我们看上面这段代码,定时器里的打印要等待3s后才能执行,那我们的第5行代码总不能傻傻地等3s才执行吧,显然这样的效率是很低的。由此,便引出了js中的事件循环机制。
同步任务和异步任务
在JS中,我们把需要运行的代码分为同步任务和异步任务。简单来说,像console.log(1)
这样可以立即执行的任务就是同步任务,而同步任务的执行顺序就是从上至下依次执行。
而那些需要耗时的任务,如
setTimeout
,setInterval
- 接口请求
Ajax,Fetch
Promise.then(), Promise.catch()
,这里要注意Promise本身是同步的,只有then和catch是异步的。- ...
像这些则是异步任务。前面我们讲到JS是单线程的,所以JS中的异步任务并不是由JS引擎直接运行的,而是交给浏览器或node环境。
宏任务和微任务
异步任务又分为宏任务和微任务,常见的宏任务有:
script
(整体脚本的执行)setTimeout
setInterval
setImmediate
(会被最后执行)
它们是要延迟执行的任务。
常见的微任务有:
process.nextTick()
(node环境下会被最先执行)Promise.then() / .catch()
async/await
它们是执行上下文结束之后要立即执行的任务,会在宏任务执行完毕后立刻执行。
事件循环机制
事件循环机制遵循以下规则:
- 执行栈首先会立即执行所有的同步任务
- 当执行时遇到宏任务时,则会将其回调函数推入宏任务队列;遇到微任务时,则会将其放入微任务队列
- 同步任务执行完毕后,接着执行所有的微任务,最后是所有的宏任务
下面通过一段简单的代码示例为大家讲解一下具体顺序:
首先,这其实是一个大的宏任务,因为我们说了整个script也是一个宏任务,这里我们先忽略script看看这段代码的执行顺序是怎样的:
- 首先,
console.log(1)
是同步任务,执行栈会立即执行。 setTimeout()
是宏任务,它的回调函数会被推进宏任务队列。
-
接着,
console.log(3)
又是同步任务,执行栈会立即执行。 -
上面我们说到,Promise对象本身是同步的,
.then()/.catch()
回调函数才是异步任务,所以console.log(4)
也会被执行栈立即执行,接着来到resolve(10)
,这一行代码仅仅代表Promise对象进入成功状态,但并不会立即执行.then()中的回调函数,所以会接着执行console.log(5)
。 -
接着
.then()
是微任务,所以它会被推进微任务队列中。 -
代码执行到了最后一行
console.log(7)
,当这行同步代码执行完毕后,就会开始执行微任务队列中的微任务。 -
当执行栈为空时,微任务队列就会按照先进先出的原则把回调函数交给执行栈执行。但这里还有一个特例,
process.nextTick()
在微任务队列中会被最先执行(node环境下)。
- 当微任务队列被清空后且没有新的微任务进入,事件循环则会执行宏任务队列中的所有宏任务。这里还有一个特例
setImmediate()
一定是在当前事件循环结束后,在下一次事件循环之前执行。
所以最后的打印结果为1 3 4 5 7 6 2 .
事件循环中还有一个需要注意的点,那就是关于async/await
,await
有一个“超能力”,那就是它会把下一行和后面的代码都推进微任务队列中。而且如果await
后面跟的不是Promise
对象,那么await
将不会生效,这行代码就还是同步代码。
下面再来一段复杂一点的代码示例,让大家巩固一下对事件循环机制的理解:
这里同步代码执行完成之后会打印 3 4 6 8
。
接着process.nextTick()
会在微任务队列中被最先执行,打印7
。接着.then()
打印5
。
因为这里的await
后面不是Promise
对象,所以第17行代码是同步代码,第18行打印9
。
接着又await
一个Promise
对象,resolve(10)
之后,await
会拿到resolve
的结果,并打印出了10
。至此微任务队列被清空,开始执行宏任务队列中的任务。
因为setImmediate()
一定会在当前事件循环结束之后才执行,所以先执行setTimeout()
,打印出2
,最后打印出1
。
所以最终的结果是 3 4 6 8 7 5 9 10 2 1
。
总结
事件循环机制能够帮我们很好的处理一些同步任务和异步任务,把它弄懂之后对我们再开发中写出高效且容易维护的代码非常有帮助。我们一定要记住哪些是宏任务哪些是微任务,在脑海里也要有事件循环机制的运作流程。
转载自:https://juejin.cn/post/7412936177043718182