事件循环
简单说,JS 中替代线程的任务调度机制。
事件循环是一种编程模型,用于处理异步事件和I/O操作。它的核心思想是,程序在一个无限循环中等待事件的发生,一旦有事件发生,就会执行相应的处理程序。
JavaScript 的事件循环(Event Loop)是一种单线程执行模型,用于处理异步事件和回调函数。事件循环机制用于将 JavaScript 代码分成一个个任务,这些任务会被添加到一个任务队列中,并按照特定的顺序执行。事件循环分为同步任务和异步任务两种类型。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入 Event Queue 。主线程内的任务执行完毕为空,会去 Event Queue 读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
特点
- JS 引擎是单线程的
- Event Loop 是 javascript 的执行机制
- 微任务优于宏任务先执行
线程
事件循环
任务队列
宏任务 task
微任务 job
流程:
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染 UI
- 然后开始下一轮 Event loop,执行宏任务中的异步代码
为什么用单线程
JavaScript 是用于实现网页交互逻辑的,涉及到 dom 操作,如果多个线程同时操作需要做同步互斥的处理,为了简化就设计成了单线程
单线程由于存在阻塞问题,因此需要调度机制来解决
支持异步
支持优先级(高优插入)
浏览器和 Node.js 的 EventLoop 为什么这么设计? - 首席CTO笔记
Node
高性能需求,更复杂
宏任务分为 6 种,Timer 优先
微任务分为 nextTick 和其它微任务,nextTick 优先
console.log('a')
setTimeout(function() {
console.log('b')
process.nextTick(function() {
console.log('c')
})
new Promise(function(resolve) {
console.log('d')
resolve()
}).then(function() {
console.log('e')
})
})
process.nextTick(function() {
console.log('f')
})
new Promise(function(resolve) {
console.log('g')
resolve()
}).then(function() {
console.log('h')
})
process.nextTick(function() {
console.log('ff');
})
setTimeout(function() {
console.log('i')
process.nextTick(function() {
console.log('j')
})
new Promise(function(resolve) {
console.log('k')
resolve()
}).then(function() {
console.log('l')
})
})
浏览器
a, g, f, h,
b, d, c, e
i, k, j, l
Node 6
a, g, f, h,
b, d, i, k
c, j, e, l
同级先执行,如两个 nextTick 或两个 then 是同级
如何解释Event Loop面试官才满意? - 知乎
Online Node Compiler - Online Node Editor - Online Node IDE - Node Coding Online - Practice Node Online - Execute Node Online - Compile Node Online - Run Node Online
浏览器 - Event loop - 《前端面试之道》 - 书栈网 · BookStack
setTimeout(myCallback, 1000);
setTimeout(myCallback, 0); // 等执行栈清空后执行
并不表示,1s 后执行,而是 1s 后将 myCallback 添加到事件循环的队列,排队执行
浏览器与 Node 差异
Node.js 也采用了事件循环模型,但是和浏览器中的 JavaScript 事件循环有一些区别。以下是一些主要的差异:
- Node.js 中的事件循环是基于 libuv 库实现的,而不是浏览器中的事件循环实现。
- 在浏览器中,事件循环的主要任务是处理 DOM 事件和执行异步操作,而在 Node.js 中,事件循环的主要任务是处理 I/O 操作。
- 在 Node.js 中,事件循环会在运行时自动创建一个事件队列,同时提供了一些内置模块,如 fs 和 net 模块,用于处理各种 I/O 操作。
- 在浏览器中,通过 setTimeout() 和 setInterval() 函数可以向事件队列中添加任务,而在 Node.js 中,可以使用类似 setImmediate()、process.nextTick()、setTimeout() 和 setInterval() 等函数来添加任务。
- 在 Node.js 中,事件循环可以使用 cluster 模块来创建多个进程,并在这些进程之间共享任务队列,以提高程序的性能和可靠性。
宏任务
宏任务 (macrotask) 是指需要在事件循环队列中排队等待执行的任务。通常,宏任务是由浏览器或 Node.js 运行时环境提供的 API 触发的,例如:
- setTimeout 和 setInterval
- I/O 操作(如文件读取、数据库操作等)
- UI 渲染
- 事件监听器
- postMessage 和 MessageChannel
需要注意的是,由于 JavaScript 是单线程执行的,因此在事件循环队列中只有一个宏任务在被执行,其他的任务(包括微任务)都要等待当前宏任务执行完毕之后才能被执行。另外,如果在当前宏任务执行期间触发了新的宏任务,那么它将被加入到事件循环队列的尾部等待执行,而不会中断当前正在执行的宏任务。
微任务
以下是 JavaScript 中常见的微任务:
- Promise 状态变化时的回调函数 (then, catch, finally)
- process.nextTick() (Node.js 环境中)
- Object.observe() (已废弃)
- MutationObserver (DOM 变化观察器)
- queueMicrotask() (在 ECMAScript 2019 标准中引入,可以使用该方法将任务添加到微任务队列中)
需要注意的是,虽然微任务的执行顺序比宏任务的执行顺序更高,但是它们不会打断正在执行的宏任务,只有当宏任务执行完后才会执行微任务队列中的任务。
题目:说出执行顺序
process.nextTick(() => console.log(5))
Promise.resolve().then(() => console.log(1))
;(async () => console.log(2))()
;(() => console.log(3))()
setTimeout(() => console.log(4))
// 2 3 5 1 4
- L0: nextTick 无论放哪,都会在同步代码和任务间执行
- L1:安排微任务。 完成所有同步 JS 后执行,类型与 L0 相同,排队
- L2:IIFE 后执行。 它是一个异步功能,但仍然同步执行(没有等待!),没有用 return
- L3:一个 IIFE,同步。
- L4:一个任务,所以它将在微任务之后运行。 所以 2-3-5-1-4
同步代码 -> node nextTick -> 微任务 promise -> 任务 setTimeout
requestAnimationFrame
常见误解
- 任务队列用了 quene?
不是,用的 set,轮询的方法,不同类型任务执行顺序不一样
前端面试题:说说事件循环机制(满分答案来了) - 山月行的个人空间 - OSCHINA - 中文开源技术交流社区
第 10 题:常见异步笔试题,请写出代码的运行结果 · Issue #7 · Advanced-Frontend/Daily-Interview-Question
Jake Archibald: In The Loop - JSConf.Asia - YouTube
第 25 题:浏览器和 Node 事件循环的区别 · Issue #26 · Advanced-Frontend/Daily-Interview-Question · GitHub
Promise.resolve().then(() => console.log(1))
;(async () => {
await console.log(6)
return console.log(2)
})()
;(() => console.log(3))()
setTimeout(() => console.log(4))
process.nextTick(() => console.log(5))
// 6 3 5 1 2 4
事件循环以及浏览器渲染时机 - 简书事件循环是如何影响页面渲染的? - 云+社区 - 腾讯云
为什么需要事件循环
事件循环是现代编程语言中常用的一种并发处理模型,它的主要作用是处理异步事件和回调函数,以提高程序的性能和响应能力。
传统的编程模型中,通常采用多线程或多进程的方式处理并发任务。这种模型的缺点是线程或进程的创建和销毁需要消耗大量的资源,而且在线程或进程之间的数据共享和同步也非常困难。另外,由于线程或进程数量的限制,这种模型很难扩展到处理大量的并发任务。
相比之下,事件循环模型是一种更加轻量级和高效的并发处理模型。在事件循环模型中,所有任务都在一个单独的线程中运行,通过任务队列来管理任务的执行顺序。当一个任务完成后,事件循环会自动从队列中取出下一个任务进行处理,从而实现任务的异步执行。这种模型不仅能够提高程序的性能和响应能力,而且还可以轻松处理大量的并发任务,因为它不需要创建大量的线程或进程。
另外,事件循环还能够处理很多其他的编程场景,如动画效果、用户输入事件等。在这些场景下,事件循环可以帮助我们实现实时的交互和响应,提高用户体验和应用程序的质量。
因此,事件循环是现代编程语言中非常重要的一个概念,对于提高程序性能和响应能力、处理并发任务、实现实时交互等都具有重要的作用。
interview
详细说明 Event Loop https://xie.infoq.cn/article/934dc016b4f65ba60252bb713#:~:text=约 39 分钟-,详细说明 Event loop,-众所周知 JS 是
事件循环阻塞
如果有正在进行的 JS 任务,无论时间长短,渲染就不会进行,对用户交互也会有影响
解决:
- 长耗时任务分解,通过定时器或者Web Worker等机制分批执行
- 进度提示
- 延迟执行:比如创建自定义事件,等事件冒泡在各层完成后,再 dispatch