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

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

总结

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

相关推荐
meilindehuzi_a1 分钟前
Node.js + LangChain.js + Milvus 实战:从 EPUB 入库到《天龙八部》RAG 问答系统
javascript·langchain·node.js
勇往直前plus25 分钟前
Vue3(篇四)单页面应用到路由管理
javascript·typescript·前端框架·vue
Chengbei111 小时前
云安全漏洞挖掘SKILL、一站式云漏洞挖掘工具,支持S3爆破、IMDS探测、K8s检测与AK/SK权限利用
前端·人工智能·网络安全·云原生·容器·kubernetes·系统安全
不简说2 小时前
JS 代码技巧 vol.9 — 20 个设计模式在真实项目里的应用
前端·javascript·github
CoderLiu2 小时前
Agent 工程的下一层:为什么「把流程写成图」还不够,还需要 Graph Engineering
前端·人工智能·后端
勇往直前plus2 小时前
Vue3(篇三) Element Plus
前端·javascript·typescript·vue
爱勇宝2 小时前
《道德经》第 8 章:真正高级的能力,像水一样成事
前端·后端·程序员
Revolution612 小时前
数组本身没有 map,为什么还能直接调用:原型链怎样查找属性
前端·javascript·面试
bonechips2 小时前
RAG实战(一):EPUB 加载、文本切割与向量入库
前端·数据库
swipe2 小时前
10|(前端转全栈)库存扣减为什么最容易出事故?SKU、并发与原子更新
前端·后端·全栈