JS的事件循环(eventloop)是怎么运作的?

事件循环、eventloop、运行机制 这三个术语其实说的是同一个东西,运行过程如下:

1.首先判断JS是同步还是异步,同步就进入主线程运行,异步就进入event table。

2.异步任务在event table中注册事件,当满足触发条件后(触发条件可能是延时也可能是ajax回调),被推入event queue。

3.同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中。

console.log(1) // 同步任务进入主线程
setTimeout(fun(),0)   // 异步任务,被放入event table, 0秒之后被推入event queue里
console.log(3) // 同步任务进入主线程

1、3是同步任务马上会被执行,执行完成之后主线程空闲去event queue(事件队列)里查看是否有任务在等待执行,这就是为什么setTimeout的延迟时间是0毫秒却在最后执行的原因。      //1,3,fun()

关于setTimeout有一点要注意延时的时间有时候并不是那么准确。

setTimeout(() => {
  console.log('2秒到了')
}, 2000)
wait(9999999999)

分析运行过程:

  1. console进入Event Table并注册,计时开始。
  2. 执行sleep函数,sleep方法虽然是同步任务但sleep方法进行了大量的逻辑运算,耗时超过了2秒。
  3. 2秒到了,计时事件timeout完成,console进入Event Queue,但是sleep还没执行完,主线程还被占用,只能等着。
  4. sleep终于执行完了,console终于从Event Queue进入了主线程执行,这个时候已经远远超过了2秒。

其实延迟2秒只是表示2秒后,setTimeout里的函数被会推入event queue,而event queue(事件队列)里的任务,只有在主线程空闲时才会执行。上述的流程走完,我们知道setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为console)加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于2秒。 我们还经常遇到setTimeout(fn,0)这样的代码,它的含义是,指定某个任务在主线程最早的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。但是即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。

关于setInterval: 以setInterval(fn,ms)为例,setInterval是循环执行的,setInterval会每隔指定的时间将注册的函数置入Event Queue,不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。需要注意的一点是,一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。

上面的概念很基础也很容易理解但不幸的消息是上面讲的一切都不是绝对的正确,因为涉及到Promise、async/await、process.nextTick(node)所以要对任务有更精细的定义:

宏任务和微任务

宏任务(macro-task):包括整体代码script、setTimeout、setInterval、MessageChannel、postMessage、setImmediate。
微任务(micro-task):Promise、process.nextTick、MutationObsever。

在划分宏任务、微任务的时候并没有提到async/await因为async/await的本质就是Promise。

事件循环机制到底是怎么样的? 不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同(宏任务)的Event Queue。而Promise和process.nextTick会进入相同(微任务)的Event Queue。

  1. 「宏任务」、「微任务」都是队列,一段代码执行时,会先执行宏任务中的同步代码。
  2. 进行第一轮事件循环的时候会把全部的js脚本当成一个宏任务来运行。
  3. 如果执行中遇到setTimeout之类宏任务,那么就把这个setTimeout内部的函数推入「宏任务的队列」中,下一轮宏任务执行时调用。
  4. 如果执行中遇到 promise.then() 之类的微任务,就会推入到「当前宏任务的微任务队列」中,在本轮宏任务的同步代码都执行完成后,依次执行所有的微任务。
  5. 第一轮事件循环中当执行完全部的同步脚本以及微任务队列中的事件,这一轮事件循环就结束了,开始第二轮事件循环。
  6. 第二轮事件循环同理先执行同步脚本,遇到其他宏任务代码块继续追加到「宏任务的队列」中,遇到微任务,就会推入到「当前宏任务的微任务队列」中,在本轮宏任务的同步代码执行都完成后,依次执行当前所有的微任务。
  7. 开始第三轮,循环往复...

下面用代码来深入理解上面的机制:

setTimeout(function() {
    console.log('4')
})

new Promise(function(resolve) {
    console.log('1') // 同步任务
    resolve()
}).then(function() {
    console.log('3')
})
console.log('2')

1.这段代码作为宏任务,进入主线程。

2.先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。

3.接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。

4.遇到console.log(),立即执行。

5.整体代码script作为第一个宏任务执行结束。查看当前有没有可执行的微任务,执行then的回调。 (第一轮事件循环结束了,我们开始第二轮循环。)

6.从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。 执行结果:1 - 2 - 3 - 4


作者:船长_
链接:https://juejin.im/post/5c148ec8e51d4576e83fd836
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值