JavaScript 运行机制 Event Loop 学习

本文介绍了JavaScript的单线程特性、任务队列、宏任务与微任务的区别,并通过实例详细解析了Event Loop的工作机制,阐述了一次事件循环的步骤,帮助理解JavaScript异步执行的本质。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个事件机制我看了好多的文章,有不少被指出有理解错误或问题,我一直对这个东西处于迷迷糊糊的状态,今天看到一道题,决定好好研究一下。本文只是我自己的的学习总结,不免有问题,希望看到问题的朋友批评指正。
工具:Loupe我们可以通过使用 Loupe(Loupe是一种可视化工具,可以帮助您了解JavaScript的调用堆栈/事件循环/回调队列如何相互影响)工具来了解上面代码的执行情况。

1. JavaScript是单线程
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
2.任务队列
所有任务分为两种,一种同步任务,一种是异步任务,同步任务会在主线程排队执行,遇到异步事件不会等待它返回结果,而是将这个事件挂起,继续执行执行栈中的其他任务。当异步事件返回结果,将它放到事件队列中,被放入事件队列不会立刻执行起回调,而是等待当前执行栈中所有任务都执行完毕,主线程空闲状态,主线程会去查找事件队列中是否有任务,如果有,则取出排在第一位的事件,并把这个事件对应的回调放到执行栈中,然后执行其中的同步代码。
在这里插入图片描述
3.宏任务和微任务
页面渲染事件,各种IO的完成事件等随时被添加到任务队列中,一直会保持先进先出的原则执行,我们不能准确地控制这些事件被添加到任务队列中的位置。但是这个时候突然有高优先级的任务需要尽快执行,那么一种类型的任务就不合适了,所以引入了微任务队列。
宏任务:macrotask
script(整体代码)
setTimeout()
setInterval()
postMessage(window.postMessage() 方法可以安全地实现跨源通信,比如H5和app通信)
I/O
UI交互事件
微任务: microtask
new Promise().then(回调)
MutationObserver(html5 新特性)
1. 异步任务的返回结果会被放到一个任务队列中,根据异步事件的类型,这个事件实际上会被放到对应的宏任务和微任务队列中去。
在当前执行栈为空时,主线程会查看微任务队列是否有事件存在。
2. 当前执行栈执行完毕后时会立刻处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
3. 关于macrotask和microtask的理解,光这样看会有些晦涩难懂,结合事件循坏的机制理解清晰很多,下面这张图可以说是介绍得非常清楚了。
在这里插入图片描述
总结起来,一次事件循环的步骤包括:

  1. 检查macrotask队列是否为空,非空则到2,为空则到3
  2. 执行macrotask中的一个任务
  3. 继续检查microtask队列是否为空,若有则到4,否则到5
  4. 取出microtask中的任务执行,执行完成返回到步骤3
  5. 执行视图更新

上代码:

console.log('start')

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

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

console.log('end')

1.全局代码压入执行栈执行,输出 start
2.setTimeout压入 macrotask队列,promise.then 回调放入 microtask队列,最后执行 console.log(‘end’),输出 end
3.调用栈中的代码执行完成(全局代码属于宏任务),接下来开始执行微任务队列中的代码,执行promise回调,输出 promise1, promise回调函数默认返回 undefined, promise状态变成 fulfilled ,触发接下来的 then回调,继续压入 microtask队列,此时产生了新的微任务,会接着把当前的微任务队列执行完,此时执行第二个 promise.then回调,输出 promise2
4.此时,microtask队列 已清空,接下来会会执行 UI渲染工作(如果有的话),然后开始下一轮 event loop, 执行 setTimeout的回调,输出setTimeout

最后的执行结果如下
start
end
promise1
promise2
setTimeout

4.总结
事件循环是js实现异步的核心
每轮事件循环分为3个步骤:
a) 执行macrotask队列的一个任务
b) 执行完当前microtask队列的所有任务
c) UI render

浏览器只保证requestAnimationFrame的回调在重绘之前执行,没有确定的时间,何时重绘由浏览器决定

【参考资料】
1.JavaScript中的Event Loop(事件循环)机制
2.HTML系列:macrotask和microtask
3.深入理解js事件循环机制(浏览器篇)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值