js的事件循环机制是面试中常见的js问题, 本文将带你彻底理清js事件循环机制中,代码究竟是按照怎么样的顺序运行的。
一、 js是单线程语言
JavaScript 是一种单线程编程语言,这意味着它在同一时间只能执行一个任务,或者说,在任何给定的时间里,JavaScript 引擎中只会有一个执行线程来处理JavaScript脚本。这个执行线程遵循从上至下逐行执行代码的原则。就和人一样当前时间你的注意力只能集中在一件事情上面,等到这件事情做完之后才会去做下一件事。
单线程好处
-
避免复杂的并发控制:
- 在多线程环境中,开发者需要处理线程间的通信、同步和锁定等并发问题,这会增加编程复杂性和出错的可能性。JavaScript通过单线程设计简化了这些问题,使得开发者无需关注这些复杂的并发控制机制。
-
提供一致的运行时行为:
单线程模型有助于确保程序的行为可预测,尤其是在面对共享状态时,能够保证执行顺序的一致性,减少因并发引起的不确定性。
在浏览器环境中,JavaScript主要用于动态更新页面内容、处理用户交互和操作DOM(文档对象模型)。DOM API并非线程安全的,如果有多个线程同时修改DOM结构,将会导致不可预知的结果和严重错误。因此,通过单线程设计,JavaScript确保任何时候只有一个线程访问和修改DOM,从而避免了竞态条件和同步问题。
-
内存占用小,占用资源少:
- 在资源有限的客户端环境中(如浏览器),如果为每一个JavaScript任务创建单独的线程,可能会导致资源消耗过大,影响性能和用户体验。
二、异步
有些代码执行时间特别长的话,就需要等待很久,比如说网络请求。但是像人一样,我们在处理一个大任务的时候,会先将那些不怎么需要时间的任务先处理完成,回过头来再接着处理那些比较麻烦的事情。js当中也有这样的机制也就是异步。
js当中的异步机制是就是我们所说的事件循环机制来实现
三、 事件循环机制 Event Loop
事件循环是JavaScript执行上下文中的一种机制,用于处理异步操作。它的核心思想是将所有的异步任务放入一个队列中,然后按照队列中的顺序依次执行,直到队列为空为止。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
宏任务,微任务
按照代码的书写顺序逐行执行,不需要耗时的代码,(耗时是相对的),我们称为同步代码 . 需要耗时的代码我们称为异步代码 ,异步代码分为,宏任务与微任务
- 宏任务 是一类相对低优先级的异步任务,它们按照一定的顺序在一个或多个任务队列中排队等待执行,每个宏任务执行结束之后,才会进入下一个事件循环阶段。 常见的宏任务来源:
- setTimeout
- script的整体代码
- setInterval
- I/O操作
- UI渲染
- setImmediate()(node.js环境)
- 微任务 - 宏任务是一类相对低优先级的异步任务,它们按照一定的顺序在一个或多个任务队列中排队等待执行,每个宏任务执行结束之后,才会进入下一个事件循环阶段。常见的微任务来源:
- Promise的then()、catch()、finally()方法
- async/await(其源码中也是靠Promise实现的,也就是Promise)
- promise.nextTick() (node.js环境)
- MutationObserver(浏览器环境)
执行顺序
- 1.先执行同步代码,所有同步代码都在主线程上执行,形成一个执行栈。
- 2.当遇到异步任务时,会将其挂起并添加到任务队列中,宏任务放入宏任务队列,微任务放进微任务队列。
- 3.当执行栈为空时,事件循环从任务队列中取出一个任务,加入到执行栈中执行。
- 4.重复上述步骤,直到任务队列为空。

例子
js
async function async1() {
console.log('async1 start');
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
console.log('script start')
async1()
setTimeout( ()=> {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('then1')
})
.then(function () {
console.log('then2')
})
console.log('script end')
解析
首先开始执行script整段代码
- 执行同步代码console.log('script start')
- 发现函数async1,执行内部逻辑==注意async函数并不是微任务而是其await关键字后面代码==
- 执行同步代码console.log('async1 start');
- await关键字将后面所有代码放入微任务队列,微任务队列:async2(), console.log('async1 end')
- 发现setTimeout放入宏任务队列
- 执行newPromise构造函数中的逻辑
- 执行同步代码 console.log('Promise') resolve()
- 发现两个then,依次放入微任务队列
- 此时微任务队列:
- async2(),
- console.log('async1 end')
- 第一个 then()
- 第二个then()
- 此时微任务队列:
- 同步代码执行完毕,查看微任务队列
- 依次执行:
- async2 end
- async1 end
- then1
- then2
- 依次执行:
- 开启下一轮事件循环,查看宏任务队列,执行
- 结果: setTimeOut
- 打印结果

总结
概括 js是一门单线程语言,通过事件循环机制实现异步执行代码。
单线程的好处:
- 占用资源少,只有一个线程
- 相比于多线程,a.不用切换执行上下文,速度快;b.对于访问DOM结构获取变量的时候无需考虑复杂的并发
时间循环机制
- 宏任务:整个script代码、setTimeout、setInterval、I/O操作、UI渲染setImmediate(node.js)
- 微任务:Promise的then、catch、finally;async/await、promise.nextTick(node.js)、Mutation.Observe(浏览器)
- 执行顺序:宏任务->清空微任务队列->继续下一个宏任务,直到所有队列为空