js-事件循环机制

6dianbiqi Lv2

JS事件循环机制

1.JS 的运行机制

JS是一门单线程的语言。在设计初期,由于JS是运行在浏览器端的脚本语言,目的就是为了实现与页面的动态交互,其核心就是 DOM 操作,这就决定了其必须使用单线程去处理脚本信息,从而避免对同一 DOM 元素同时进行操作时产生冲突问题。

若是遇到耗时操作,页面便会产生堵塞。例如请求接口返回数据慢,图片未加载完成等等。这样显然是不合理也不实用的,因此异步模式应运而生。

1.1 同步(阻塞)

同步执行模式就是JS会严格按照原本单线程逻辑,从上到下,从左到右的执行代码逻辑。

同步的优点在于:

  1. 简单性:单线程代码容易编写、调试和维护,不容易出现多线程竞争的问题。
  2. 安全性:多线程需要共享内存,容易造成数据竞争等问题。JavaScript 作为一种脚本语言,通常运行在浏览器环境中,存在众多恶意脚本的威胁。如果 JavaScript 是多线程的,恶意脚本可能会通过共享内存的方式修改其他脚本的数据,从而造成安全问题。
  3. 可预测性:单线程可以确保事件的执行顺序是可预测的,从而能够避免一些复杂的并发场景。

1.2 异步(非阻塞)

JS 中采取的异步策略是:在执行 JS 代码时先逐步执行同步代码,遇到异步代码先将异步代码放到任务队列中,直到同步代码执行完毕才执行异步代码。

异步的优点在于:

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

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

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

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

2. 了解执行栈与任务队列

要学习 JS 的事件循环机制,我们就必须对【执行栈】和【任务队列】有所了解。

2.1 执行栈

执行栈,就是用来存储正在执行的代码的栈,当执行某个函数、用户点击一次鼠标、Ajax请求完成、一个图片加载完成等事件发生时,只要指定了回调函数,这些事件发生时就会进入执行栈队列中,等待主线程读取,并遵循先进先出原则。

❗ 需要注意的是,主线程跟执行栈是不同的概念,主线程规定了现在正在执行执行栈中的哪个事件

2.2 任务队列

我们知道,主线程会不停的从执行栈中读取事件,并执行完所有栈中的同步代码

🎈 而当遇到一个异步事件时,主线程会如何处理呢?

事实上,主线程并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为**任务队列(Task Queue)**。

等到主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。

根据 W3C 的最新解释:

  • 每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属于不同的队列。在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。
  • 浏览器必须准备好一个 微任务队列,微任务队列中的任务优先级最高。

在目前 chrome 的实现中,至少包含了下面的队列:

  • 延时队列:用于存放计时器到达后的回调任务,优先级「中」。
  • 交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」。
  • 微任务队列:用户存放需要最快执行的任务,优先级「最高」。

在 JS 中,任务又可以分为【同步任务】与【异步任务】

2.3 同步任务与异步任务

  • 同步任务 — 指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
  • 异步任务 — 指不进入主线程、而进入任务队列的任务,只有等主线程任务全部执行完毕,任务队列的任务才会进入主线程执行。

异步任务又可以分为【宏任务】和【微任务】。

如下表:

pFMBHSS.png

同时,需要注意的是:

这里的 Promise 仅仅指 Promise 的thencatch方法,而创建 Promise(new Promise()中的内容)为同步任务。

宏任务微任务 的区别在于:

微任务队列唯一的,在整个事件循环中,仅存在一个,并且同一轮事件循环中的微任务会按顺序依次执行。

宏任务 存在一定的优先级(用户I/O部分优先级更高)。且一轮事件循环中,只执行一个宏任务

3. JS 事件循环机制

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

JS 事件循环机制 指的就是 JavaScript 运行时环境(ECMAScript 规范定义的)按照一定的规则处理代码中的异步操作和事件的机制。

4. JS 任务执行过程

在这里插入图片描述

✨ 不难看出,实际上的执行顺序为:同步任务 → 微任务 → 宏任务

具体来说,有以下几个步骤:

  1. 先执行所有 同步任务,碰到 异步任务 时,将其放入 Event Table 中,并为其注册回调函数,当其指定的事件发生时,将该回调加入 Event Queue

    ❗ 需要注意的是:对于 宏任务微任务 而言,虽然都是放入 Event Queue,但这两个并不是同一个队列!微任务 会被放入 微任务队列宏任务 会被放入其对应类型任务的队列。

  2. 同步任务 执行完毕,开始执行当前所有的 异步任务,即从 Event Queue 取出回调函数进入主线程执行。

  3. 选择最先进入队列的 宏任务(通常是 script 整体代码),如果有则执行。待其结束后,执行 微任务队列 里面所有的 微任务

  4. 检查是否有可执行的 宏任务,有则执行。

  5. 检查 微任务队列 中是否有可执行的 微任务,有则全部执行。

  6. 再次检查是否有可执行的 宏任务,有则执行,完成后,检查 微任务队列 中是否有可执行的 微任务,有则全部执行……依次类推直到 任务队列 为空。

在上述过程中,只有第 3 步到第 6 步的这个循环,被称为 Event Loop,即事件循环

在事件循环中,每一次循环称为一次 tick,每一次 tick 中需要执行的任务如下:

  • 选择最先进入队列的 宏任务(通常是 script 整体代码),如果有则执行;
  • 检查是否存在 微任务,如果存在则不停的执行,直至清空 微任务队列
  • 更新 render(每一次事件循环,浏览器都可能会去更新渲染);

如下图:

image

实例参考:事件循环实例

总结

事件循环是 JavaScript 实现异步的一种方法,也是 JavaScript 的执行机制。

事件循环主要是针对解决宏任务一次事件循环只能处理一个宏任务,执行宏任务的时候会进行一次轮询,看有没有微任务,如果有微任务将会把微任务执行全部执行完毕,再执行宏任务。

  • 标题: js-事件循环机制
  • 作者: 6dianbiqi
  • 创建于 : 2024-07-02 18:46:06
  • 更新于 : 2024-07-02 18:53:43
  • 链接: https://github.com/xz719/2024/07/02/js-事件循环机制/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论