前言:
JavaScript的事件循环机制(Event-Loop) 是其异步非阻塞执行的核心。它是一种处理事件和执行任务的机制,使得JavaScript能够在单线程环境下处理大量的I/O操作和异步任务。本文将用简明的语言带领大家一起探索JavaScript的Event-Loop,解析其工作原理以及如何优雅地处理异步操作。
通过本文的阐述,大家能够更好地理解event-loop的工作原理,从而更好地应用它来解决实际的开发问题。
进程与线程:
什么是进程、线程?
进程(Process)是指正在运行的程序的实例。一个进程包括了程序的代码、数据、以及程序运行时的各种状态,例如寄存器的内容、堆栈、打开的文件等。
简单来说,进程就是一个时间片段,CPU运行指令和保存上下文所需要的时间。通常浏览器上每开一个tab页就是一个进程,所以浏览器上是多进程的。
而线程是进程的执行单元,一个进程可以包含多个线程。描述了一段指令执行所需的时间。许多编程语言都支持多线程编程,也就是多线程语言,例如我们熟知的Java、C++、Python。这里主要考虑到允许程序员创建和管理多个线程以实现并发执行。
------ 但是我们今天的主角JS是单线程语言。
为什么?
JavaScript被设计为单线程语言的原因主要与其最初的应用场景有关,即在浏览器中执行脚本。而在浏览器中运行js代码就会产生一些问题,其一就是JS能修改DOM,如果设计为多线程,多个脚本同时操纵DOM会造成不安全的渲染。通过设计为单线程,可以避免这些复杂性,确保对DOM的操作是线性的、可预测的。
那么单线程会带来什么呢?其一就是我们常听到的异步操作。
异步
JS执行代码时秉承它作为单线程的原则:看见同步往下走,看见异步先不执行。为什么?主要是为了减少js对设备性能的开销。
常见的异步代码就有定时器函数、Promise构造函数:setTimeout()
、Promise.then()
。
那么执行异步代码时又会产生两个新概念:宏任务(macrotasks)和微任务(microtasks) 。这两者在异步代码执行时的优先级和执行顺序上有一些关键的区别。 简单来说就是:V8引擎会将异步代码分别归类到上述两种任务中,每个任务都有各自对应的队列来保存任务,每个队列根据先进先出的原则执行任务(代码)。那么下面我还为大家列出了一些常见的异步代码对应的任务队列:
-
宏任务(macrotask 宏任务队列来保存):
-
script 标签
-
setTimeout
-
setInterval
-
setImmediate
-
I/O :输入输出,例如点击事件中的交互
-
UI-rendering
-
微任务 (microtask 微任务队列来保存) :
-
promise.then()
-
MutationObserver()
-
Process.nextTick()
那么我再给大家列一个具体的实例方便大家记忆:
javascript
console.log('start');
//宏任务队列
setTimeout(() => {
console.log('setTimeout');
setTimeout(() => {
console.log('inner');
})
console.log('end');
}, 1000)
new Promise((resolve, reject) => {
console.log('Promise');
resolve()
})
//微任务队列
.then(() => {
console.log('then1');
})
.then(() => {
console.log('then2');
})
上述调用了setTimeout
和promise.then
方法,这两种方法很明显都是异步操作,那么区别就是setTimeout
是属于宏任务,所以会放入宏任务队列里,而promise.then
是属于微任务,会放入微任务队列里。
------ 知道了宏任务和微任务,就可以引出event-loop的概念了
Event-loop
Event-loop是V8的执行规则,也可以叫事件循环机制。这个机制可以很好的去处理同步和异步代码。那么这个机制到底是什么呢?那么这个机制主要有以下几个步骤:
- 执行同步代码
- 当执行栈为空后,去查询是否有异步代码需要执行
- 有则执行微任务
- 如果有需要,会渲染页面
- 执行宏任务(这也叫下一轮event-loop的开启)
那么上述代码的答案到底是什么呢?下面我会用作图的方式给大家详细分析,帮助大家理解:

相信大家也都成功的理解了这个事件循环机制,这里要注意的点是总共有三次循环,注意因为宏任务的执行在循环的末尾,而宏任务setTimeout方法的调用会进入一个新的执行栈,所以会生成一次新的循环。
那么总的来说,事件循环机制是为了处理异步操作和事件的执行顺序而设计的。这一机制使得在JS在单线程环境下能够有效地处理异步操作,保障了程序的流畅执行,同时确保了良好的用户体验。
经典面试题
JS的事件循环机制是面试时经常被问到的一个考点,非常非常的重要。那么小编也为大家附上一道经典的面试题用于大家强化理解。
javascript
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
注意这里的async
是es7新增的一种异步写法,其作用就是将一个函数声明为异步,该函数返回一个 Promise 对象 ,而函数内部的操作可以使用 await
关键字来处理异步任务。在async声明的函数中await类似于.then,可以把修饰的代码强行转化成同步,其后的代码统统推入微任务队列。
具体大家可以参考MDN文档:async 函数 - JavaScript | MDN (mozilla.org))
那么我依旧通过作图的方式为大家讲解这道题:

最终结果
- script start
- async2 end
- Promise
- script end
- async1 end
- promise1
- promise2
- setTimeout
那么大家可以按照图中的思路自己推一遍得到正确答案!
总结
希望本文对大家的学习有所帮助,有任何的疑问都欢迎大家在评论留言!