(轻松掌握)异步交响曲:迷人的事件循环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

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

总结

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

相关推荐
蓝胖子的多啦A梦3 分钟前
【前端】VUE+Element UI项目 页面自适应横屏、竖屏、大屏、PDA及手机等适配方案
前端·javascript·elementui·html·前端页面适配
掘金安东尼5 分钟前
前端周刊431期(2025年9月8日–9月14日)
前端·javascript·github
Bear on Toilet9 分钟前
继承类模板:函数未在模板定义上下文中声明,只能通过实例化上下文中参数相关的查找找到
开发语言·javascript·c++·算法·继承
风若飞12 分钟前
npm ERR! code CERT_HAS_EXPIRED
前端·npm·node.js
北城笑笑19 分钟前
NodeJS 8 ,从 0 到 1:npm 包发布与更新全流程指南( 含多场景适配与踩坑总结 )
前端·npm·node.js·github
Mike_jia20 分钟前
如何找回Harbor密码
前端
码码哈哈0.020 分钟前
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚
前端·npm·node.js
浩男孩21 分钟前
🍀简简单单结合 hooks 优雅使用弹窗🚀🚀
前端
江城开朗的豌豆22 分钟前
Axios拦截器:给你的请求加上"双保险"!
前端·javascript·react.js
晓得迷路了28 分钟前
栗子前端技术周刊第 98 期 - NPM 生态遭受攻击、Rspack 1.5.3、Storybook 10 beta...
前端·javascript·css