JavaScript:事件循环机制(EventLoop)

一、理解进程、线程

进程是操作系统中的基本概念之一,指的是一个正在运行中的程序,包括了程序的执行代码、数据、资源等。操作系统为每个进程分配一定的系统资源,例如内存空间、文件和设备等,以便进程能够正常运行。

线程是进程中的一个执行流程,可以看作是进程中的一个独立执行单元。每个进程中可以包含多个线程,这些线程共享进程的资源。不同于进程,线程是无法独立存在的,必须依存于进程而存在。线程通过利用进程的资源来执行任务,并可以在进程内进行切换。

简单地说,进程是资源分配和管理的最小单位,而线程是程序执行的最小单位。在多任务环境下,多个进程可以同时执行,同一个进程中的多个线程也可以同时执行。线程的切换比进程的切换开销小,因此多线程的程序更加高效。

二、理解单线程、多线程

单线程指的是程序只有一个执行线程,只能串行执行任务,即每个任务都得等前一个任务执行完毕后才能进行下一个任务。这种模式的好处是逻辑简单,开发维护成本低,但执行效率较低,特别是在处理大量数据时容易出现阻塞等问题。

多线程指的是程序同时拥有多个执行线程,每个线程可以独立运行,可以同时处理多个任务。多线程可以大大提高程序的执行效率和响应能力,特别是在需要同时处理多个任务或大量数据时非常有用。但多线程的缺点是复杂度高,容易出现并发问题,需要耗费更多的系统资源和开发成本。

单线程适用于简单的应用程序,而多线程则适用于复杂的应用程序,能够大幅提高程序的执行效率和响应能力。

三、理解渲染线程

渲染线程是指用于渲染图形的计算机进程线程。在Web浏览器中,渲染线程负责将HTML、CSS和JavaScript代码转化为网页上的可视化内容。渲染线程的主要任务是解析HTML文档,确定渲染树和绘制图像。它还负责检查和处理JavaScript代码,以便在网页上进行交互式操作。

渲染线程的作用是在不影响用户界面的情况下,尽可能快地显示用户请求的内容。它要注意避免阻塞用户界面线程,因此需要在后台运行,并尽量减少CPU和内存的使用。

在某些情况下,渲染线程可能需要等待网络请求或文件加载完成,这时可能会导致页面出现卡顿或加载缓慢的情况。为了减少这种情况的发生,渲染线程通常会使用缓存技术,以便在加载相同内容时快速显示网页。

四、理解微任务、宏任务

微任务和宏任务都是JavaScript事件循环机制中的任务类型。它们之间的区别在于执行的时机和优先级。

宏任务包括一些比较耗时的任务,例如setTimeout、setInterval、AJAX请求、DOM事件等等。这些任务执行完之后,需要等待下一次事件循环开始才能执行下一个任务。执行宏任务时,会先选取队列中最先进入队列的任务执行,直到队列为空。

微任务指的是一些比较短且需要立即执行的任务,例如Promise的then方法、MutationObserver等等。这些任务执行时,会在当前宏任务执行完毕后立即执行。当一个宏任务中产生了微任务,这些微任务会先被放到一个专门的队列中,等待当前宏任务执行完毕时再执行。而在执行这些微任务时,如果又产生了新的微任务,这些新的微任务会被放到队列的末尾,等待执行。

在事件循环开始时,先执行所有的微任务队列中的任务,直到队列为空。然后再执行宏任务队列中的任务。然后再执行微任务队列中的任务,直到队列为空。这个过程会一直重复下去,直到任务队列中和微任务队列中同时为空。

总结来说,微任务具有更高的优先级,会比宏任务优先执行。因此在编写异步代码时,应当注意使用合适的任务类型,以保证代码的正确性和效率。

六、理解同步任务、异步任务

同步任务是指代码按照顺序执行,每完成一项任务后再进行下一项任务,直到所有任务完成。在同步任务中,代码执行需要等待前一个代码执行完成后才能继续进行下一项任务。

异步任务则是指代码在执行过程中,不必等待前一个任务完成后再执行下一个任务。异步任务可以在单个线程上执行,但可以同时进行多个任务。异步任务通常使用回调函数或Promise来实现异步处理。

举个例子:假设有一个任务列表,包括读取文件、发送数据、等待响应等任务。如果使用同步任务执行,读取文件的任务必须在发送数据的任务完成后才能开始。而在异步任务中,可以在发送数据的同时执行读取文件任务,因此能够更高效地完成任务。

七、理解调用栈

调用栈是用来跟踪程序执行顺序的一种内存结构。当函数被调用时,它们的相关信息(例如局部变量,返回地址等)会被压入调用栈中,调用栈按照"先进后出"的原则运作。也就是说,当函数返回时,它们的相关信息将从栈中被弹出,以便程序继续执行。通过调用栈,我们可以跟踪程序的执行过程,查看每个函数被调用的顺序以及它们之间的关系。调用栈也是调试程序时重要的工具之一,可以帮助程序员精确定位程序中的问题。

八、JavaScript在设计时就被设计为单线程

1、简单性:单线程代码容易编写、调试和维护,不容易出现多线程竞争的问题。

2、安全性:多线程需要共享内存,容易造成数据竞争等问题。JavaScript作为一种脚本语言,通常运行在浏览器环境中,存在众多恶意脚本的威胁。如果JavaScript是多线程的,恶意脚本可能会通过共享内存的方式修改其他脚本的数据,从而造成安全问题。

3、可预测性:单线程可以确保事件的执行顺序是可预测的,从而能够避免一些复杂的并发场景。

当然,JavaScript也提供了一些非常重要的异步API,如setTimeout、setInterval、Promise、async/await等,这些API可以模拟多线程的效果,但本质上仍然是单线程。

九、JavaScript为什么需要异步

1、防止阻塞:JavaScript是单线程语言,如果所有任务都是同步执行的,当执行某个耗时操作(比如网络请求或文件读写)时,整个应用程序会被阻塞,造成用户体验不佳。

2、提升用户体验:异步编程可以使得JavaScript在执行耗时操作的同时,继续响应用户的操作,从而提升用户体验。

3、节约资源:异步编程可以更好地利用计算机资源,通过并行执行多个任务,提高执行效率。

4、支持跨平台开发:JavaScript广泛应用于Web、移动端和后端等不同平台,使用异步编程模式可以支持多种异步事件,从而使得代码具有更好的可移植性。

十、JavaScript中的异步操作有哪些

|----|--------------------------------------------------------------------------------------|
| 序号 | 操作 |
| 1 | 回调函数 |
| 2 | Promise |
| 3 | async/await |
| 4 | 事件监听 |
| 5 | 定时器 |
| 6 | XMLHttpRequest和Fetch API等网络请求 |
| 7 | Web Workers(Web Worker API提供了从主执行线程分离并在后台运行脚本的能力,即在后台运行JavaScript代码,不影响页面UI的渲染和响应能力) |
| 8 | Node.js中的异步I/O操作(如读写文件、网络请求等) |

十一、JavaScript单线程是如何实现异步的?理解JavaScript事件循环机制

有了前文基础我们来探讨本文核心事件循环。

JavaScript单线程指的是在同一时刻只能执行一个任务,任务只有在前一个任务执行完毕后才能开始执行。但是JavaScript通过事件循环机制实现了异步。

JavaScript事件循环机制指的是JavaScript运行时环境(ECMAScript规范定义的)按照一定的规则处理代码中的异步操作和事件的机制。JavaScript事件循环机制包含任务队列(Task)、微任务队列(Microtask)和宏任务队列(Macrotask)。

当JavaScript代码执行到一个异步操作或事件时,它并不会立即执行,而是将其放入对应的任务队列中。当当前任务执行完成后,在下一个事件循环的开始,JavaScript会从任务队列中取出一个任务,执行该任务。当任务执行时,可能会产生新的异步操作和事件,这些新的操作也会被放入任务队列中等待执行。

在JavaScript事件循环机制中,任务分为宏任务和微任务。

宏任务包括setTimeout、setInterval、I/O操作等;

微任务包括Promise、MutationObserver、process.nextTick等。

在每次事件循环开始时,JavaScript会先执行所有的微任务队列中的任务,然后再执行宏任务队列中的任务。

一个完整的事件循环流程包括以下步骤:

  1. 从宏任务队列中取出一个任务执行;

  2. 如果该任务中产生了微任务,将它们放入微任务队列中;

  3. 执行微任务队列中的所有任务;

  4. 检查是否需要重新渲染页面;

  5. 重复执行上述步骤,直到任务队列和微任务队列中都没有任务;

事件循环机制是指:

1、代码执行时,先执行同步任务,然后将异步任务放入任务队列中,等待执行。

2、当所有同步任务执行完毕后,JavaScript引擎会去读取任务队列中的任务。

3、将队列中的第一个任务压入执行栈中执行,执行完毕后将其出栈。

4、如此循环执行,直到任务队列中的所有任务都执行完毕。

这就是JavaScript实现异步的基本原理,通过将异步任务放到任务队列中,并通过事件循环机制来实现异步执行。

总的来说,JavaScript通过事件循环机制来实现异步操作,将异步任务放到任务队列中,然后在任务队列中等待执行,直到JavaScript引擎空闲,再将任务队列中的任务拿出来执行。

十二、练习题

1、下面哪些操作是异步的?

复制代码
下面哪些操作是异步的?

a. 将数组元素进行排序
b. 发送请求获取数据
c. 计算两个数的和
d. 读取本地文件

答案:b和d

2、下面的代码输出什么?

复制代码
console.log('start')

setTimeout(() => {
  console.log('timeout')
}, 0)

console.log('end')

答案:start, end, timeout

解析:setTimeout函数中的第二个参数表示延迟的时间,当设置为0时,setTimeout函数会被放到任务队列的末尾,等待执行栈中所有任务执行完成后再执行setTimeout函数中的回调函数。

3、下面的代码输出什么?

复制代码
console.log('start')

setTimeout(() => {
  console.log('timeout1')
}, 0)

new Promise((resolve) => {
  console.log('promise1')
  resolve()
}).then(() => {
  console.log('then1')
})

console.log('end')

答案:start, promise1, end, then1, timeout1

解析:Promise对象是同步执行的,所以会先输出promise1。但是.then()方法是异步执行的,会被放到任务队列中等待执行,因此end会先输出。然后在任务队列中执行.then()方法,输出then1。最后在任务队列中执行setTimeout中的回调函数,输出timeout1。

4、下面代码输出什么?

复制代码
console.log('start')

setTimeout(() => {
  console.log('timeout1')
  Promise.resolve().then(() => console.log('then2'))
}, 0)

new Promise((resolve) => {
  console.log('promise1')
  resolve()
}).then(() => {
  console.log('then1')
  setTimeout(() => {
    console.log('timeout2')
  }, 0)
})

console.log('end')

答案:start, promise1, end, then1, timeout1, then2, timeout2

解析:同样的,Promise对象和.then()方法是同步执行的,但是回调函数中包含的Promise对象和.then()方法时异步执行的,会被放到任务队列中等待执行。因此start, promise1, end, then1会先输出。然后在任务队列中执行setTimeout中的回调函数,输出timeout1,然后将包含的Promise对象和.then()方法放到任务队列中。在任务队列中执行.then()方法,输出then2。最后在任务队列中执行第二个setTimeout中的回调函数,输出timeout2。

5、下面代码输出什么?

javascript 复制代码
console.log('start'); // 1

setTimeout(function() {
  console.log('setTimeout'); // 5
}, 0);

Promise.resolve().then(function() {
  console.log('promise'); // 4
});

console.log('end'); // 2

这段代码中,我们依次执行了以下操作:

打印"start"
打印"end"
创建一个Promise对象,并将其添加到微任务队列中
执行Promise中的回调函数,打印"promise"
执行setTimeout中的回调函数,打印"setTimeout"
根据JavaScript事件循环机制的规则,它的执行过程如下:

全局上下文入栈,开始执行同步任务
打印"start"
全局上下文出栈
全局上下文入栈,开始执行同步任务
打印"end"
全局上下文出栈
全局上下文入栈,开始执行同步任务
创建Promise对象,并将其添加到微任务队列中
执行Promise对象中的回调函数,打印"promise"
全局上下文出栈
执行微任务队列中的任务,打印"setTimeout"
因此,最终输出结果应该是:

start
end
promise
setTimeout

6、下面代码的输出结果是什么?

javascript 复制代码
console.log(1);

setTimeout(() => {
  console.log(2);
}, 0);

Promise.resolve().then(() => {
  console.log(3);
});

console.log(4);

输出结果是 1 4 3 2。

解析:代码执行顺序为同步任务先执行,输出1,然后将异步任务放入任务队列中。setTimeout是一个宏任务,Promise.then是一个微任务。由于Promise是微任务,会优先执行,所以先输出3。然后执行完同步任务后,会依次执行微任务。所以输出顺序为1,4,3。最后执行宏任务,输出2。

7、下面代码的输出结果是什么?

javascript 复制代码
console.log("start");

setTimeout(() => {
  console.log("setTimeout");
}, 0);

Promise.resolve().then(() => {
  console.log("Promise");
});

console.log("end");

输出结果是 start end Promise setTimeout。

解析:代码执行顺序同第一题,先输出同步任务 start 和 end,然后将异步任务放入任务队列中。由于Promise.then是一个微任务,所以会优先执行,输出 Promise。接着执行完同步任务后,依次执行微任务中的 Promise。最后执行宏任务 setTimeout 输出结果。

十三、过程记录

记录一、浏览器事件循环机制还是JavaScript事件循环机制

观点一

浏览器事件循环机制和JavaScript事件循环机制是一个概念,只是叫法不同。

JavaScript事件循环机制分为浏览器和Node事件循环机制,两者的实现技术不一样。浏览器Event Loop是HTML中定义的规范,Node Event Loop是由libuv库实现。JavaScript 事件循环机制分为JS调用栈和任务队列两部分。JS调用栈是一种后进先出的数据结构,当函数被调用时,会被添加到栈中的顶部,执行完成之后就从栈顶部移出该函数,直到栈内被清空。任务队列是先进先出的数据结构,当主线程空闲时,就会去任务队列中按照顺序读取一个任务放入到栈中执行。

观点二

浏览器事件循环机制和 JavaScript 事件循环机制是密切相关的。浏览器事件循环机制是指浏览器在处理各种事件(例如用户交互、网络请求、定时器等)时,如何协调执行这些事件的机制。而 JavaScript 事件循环机制则是指 JavaScript 引擎在处理异步代码时如何协调执行这些代码的机制。实际上,JavaScript 事件循环机制是建立在浏览器事件循环机制之上的。JavaScript 引擎通过调用浏览器提供的 API 来注册事件监听器,然后在事件触发时将回调函数加入任务队列。浏览器事件循环机制会不断地从任务队列中取出回调函数并执行,直到任务队列为空。因此,可以认为浏览器事件循环机制是 JavaScript 事件循环机制的基础。

十四、欢迎交流指正

十五、参考链接

JavaScript------事件循环机制(Event Loop)浅析 - 掘金

https://www.cnblogs.com/kitebear/p/17400025.html

详解:JavaScript事件循环机制(event loop), js运行机制(执行顺序)_javascript 事件循环_陈吖的博客-CSDN博客

web前端tips:js的事件循环(Event Loop)

JavaScript 中 Event Loop 事件循环机制

事件循环机制 (EventLoop)_事件循环原理_星屿H的博客-CSDN博客

相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试