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

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

总结

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

相关推荐
2501_944711435 分钟前
JS 对象遍历全解析
开发语言·前端·javascript
发现一只大呆瓜39 分钟前
虚拟列表:支持“向上加载”的历史消息(Vue 3 & React 双版本)
前端·javascript·面试
css趣多多1 小时前
ctx 上下文对象控制新增 / 编辑表单显示隐藏的逻辑
前端
阔皮大师1 小时前
INote轻量文本编辑器
java·javascript·python·c#
lbb 小魔仙1 小时前
【HarmonyOS实战】React Native 表单实战:自定义 useReactHookForm 高性能验证
javascript·react native·react.js
_codemonster1 小时前
Vue的三种使用方式对比
前端·javascript·vue.js
寻找奶酪的mouse1 小时前
30岁技术人对职业和生活的思考
前端·后端·年终总结
梦想很大很大1 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
We་ct1 小时前
LeetCode 56. 合并区间:区间重叠问题的核心解法与代码解析
前端·算法·leetcode·typescript
张3蜂1 小时前
深入理解 Python 的 frozenset:为什么要有“不可变集合”?
前端·python·spring