(轻松掌握)异步交响曲:迷人的事件循环event-loop之谜

前言:

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');
  })

上述调用了setTimeoutpromise.then方法,这两种方法很明显都是异步操作,那么区别就是setTimeout是属于宏任务,所以会放入宏任务队列里,而promise.then是属于微任务,会放入微任务队列里。

------ 知道了宏任务和微任务,就可以引出event-loop的概念了

Event-loop

Event-loop是V8的执行规则,也可以叫事件循环机制。这个机制可以很好的去处理同步和异步代码。那么这个机制到底是什么呢?那么这个机制主要有以下几个步骤:

  1. 执行同步代码
  2. 当执行栈为空后,去查询是否有异步代码需要执行
  3. 有则执行微任务
  4. 如果有需要,会渲染页面
  5. 执行宏任务(这也叫下一轮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))

那么我依旧通过作图的方式为大家讲解这道题:

最终结果

  1. script start
  2. async2 end
  3. Promise
  4. script end
  5. async1 end
  6. promise1
  7. promise2
  8. setTimeout

那么大家可以按照图中的思路自己推一遍得到正确答案!

总结

希望本文对大家的学习有所帮助,有任何的疑问都欢迎大家在评论留言!

相关推荐
gis收藏家15 分钟前
从稀疏数据(CSV)创建非常大的 GeoTIFF(和 WMS)
前端
程序视点29 分钟前
望言OCR 2025终极评测:免费版VS专业版全方位对比(含免费下载)
前端·后端·github
NUC_Dodamce1 小时前
Cocos3x 解决同时勾选 适配屏幕宽度和 适配屏幕高度导致Widget组件失效的问题
开发语言·javascript·ecmascript
五点六六六1 小时前
前端常见的性能指标采集
前端·性能优化·架构
吳所畏惧1 小时前
NVM踩坑实录:配置了npm的阿里云cdn之后,下载nodejs老版本(如:12.18.4)时,报404异常,下载失败的问题解决
前端·windows·阿里云·npm·node.js·batch命令
陈随易1 小时前
AI新技术VideoTutor,幼儿园操作难度,一句话生成讲解视频
前端·后端·程序员
Pedantic1 小时前
SwiftUI 按钮Button:完整教程
前端
前端拿破轮2 小时前
2025年了,你还不知道怎么在vscode中直接调试TypeScript文件?
前端·typescript·visual studio code
代码的余温2 小时前
DOM元素添加技巧全解析
前端
JSON_L2 小时前
Vue 电影导航组件
前端·javascript·vue.js