js-事件循环机制
JS事件循环机制
1.JS 的运行机制
JS是一门单线程的语言。在设计初期,由于JS是运行在浏览器端的脚本语言,目的就是为了实现与页面的动态交互,其核心就是 DOM 操作,这就决定了其必须使用单线程去处理脚本信息,从而避免对同一 DOM 元素同时进行操作时产生冲突问题。
若是遇到耗时操作,页面便会产生堵塞。例如请求接口返回数据慢,图片未加载完成等等。这样显然是不合理也不实用的,因此异步模式应运而生。
1.1 同步(阻塞)
同步执行模式就是JS会严格按照原本单线程逻辑,从上到下,从左到右的执行代码逻辑。
同步的优点在于:
- 简单性:单线程代码容易编写、调试和维护,不容易出现多线程竞争的问题。
- 安全性:多线程需要共享内存,容易造成数据竞争等问题。JavaScript 作为一种脚本语言,通常运行在浏览器环境中,存在众多恶意脚本的威胁。如果 JavaScript 是多线程的,恶意脚本可能会通过共享内存的方式修改其他脚本的数据,从而造成安全问题。
- 可预测性:单线程可以确保事件的执行顺序是可预测的,从而能够避免一些复杂的并发场景。
1.2 异步(非阻塞)
JS 中采取的异步策略是:在执行 JS 代码时先逐步执行同步代码,遇到异步代码先将异步代码放到任务队列中,直到同步代码执行完毕才执行异步代码。
异步的优点在于:
防止阻塞:JavaScript 是单线程语言,如果所有任务都是同步执行的,当执行某个耗时操作(比如网络请求或文件读写)时,整个应用程序会被阻塞,造成用户体验不佳。
提升用户体验:异步编程可以使得 JavaScript 在执行耗时操作的同时,继续响应用户的操作,从而提升用户体验。
节约资源:异步编程可以更好地利用计算机资源,通过并行执行多个任务,提高执行效率。
支持跨平台开发:JavaScript 广泛应用于 Web、移动端和后端等不同平台,使用异步编程模式可以支持多种异步事件,从而使得代码具有更好的可移植性。
2. 了解执行栈与任务队列
要学习 JS 的事件循环机制,我们就必须对【执行栈】和【任务队列】有所了解。
2.1 执行栈
执行栈,就是用来存储正在执行的代码的栈,当执行某个函数、用户点击一次鼠标、Ajax请求完成、一个图片加载完成等事件发生时,只要指定了回调函数,这些事件发生时就会进入执行栈队列中,等待主线程读取,并遵循先进先出原则。
❗ 需要注意的是,主线程跟执行栈是不同的概念,主线程规定了现在正在执行执行栈中的哪个事件。
2.2 任务队列
我们知道,主线程会不停的从执行栈中读取事件,并执行完所有栈中的同步代码。
🎈 而当遇到一个异步事件时,主线程会如何处理呢?
事实上,主线程并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为**任务队列(Task Queue)**。
等到主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。
根据 W3C 的最新解释:
- 每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属于不同的队列。在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。
- 浏览器必须准备好一个
微任务队列
,微任务队列中的任务优先级最高。在目前 chrome 的实现中,至少包含了下面的队列:
- 延时队列:用于存放计时器到达后的回调任务,优先级「中」。
- 交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」。
- 微任务队列:用户存放需要最快执行的任务,优先级「最高」。
在 JS 中,任务又可以分为【同步任务】与【异步任务】
2.3 同步任务与异步任务
同步任务
— 指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。异步任务
— 指不进入主线程、而进入任务队列的任务,只有等主线程任务全部执行完毕,任务队列的任务才会进入主线程执行。
异步任务又可以分为【宏任务】和【微任务】。
如下表:
同时,需要注意的是:
这里的 Promise 仅仅指 Promise 的then
、catch
方法,而创建 Promise(new Promise()
中的内容)为同步任务。
宏任务
与 微任务
的区别在于:
微任务队列
是唯一的,在整个事件循环中,仅存在一个,并且同一轮事件循环中的微任务会按顺序依次执行。
而 宏任务
存在一定的优先级(用户I/O部分优先级更高)。且一轮事件循环中,只执行一个宏任务。
3. JS 事件循环机制
根据前面的内容,我们知道,当 JS 代码执行到一个异步操作或事件时,它并不会立即执行,而是将其放入对应的任务队列中。当当前任务执行完成后,在下一个事件循环的开始,JavaScript 会从任务队列中取出一个任务,执行该任务。当任务执行时,可能会产生新的异步操作和事件,这些新的操作也会被放入任务队列中等待执行。
而 JS 事件循环机制
指的就是 JavaScript 运行时环境(ECMAScript 规范定义的)按照一定的规则处理代码中的异步操作和事件的机制。
4. JS 任务执行过程
✨ 不难看出,实际上的执行顺序为:同步任务 → 微任务 → 宏任务
具体来说,有以下几个步骤:
先执行所有
同步任务
,碰到异步任务
时,将其放入Event Table
中,并为其注册回调函数,当其指定的事件发生时,将该回调加入Event Queue
中❗ 需要注意的是:对于
宏任务
与微任务
而言,虽然都是放入Event Queue
,但这两个并不是同一个队列!微任务
会被放入微任务队列
;宏任务
会被放入其对应类型任务的队列。同步任务
执行完毕,开始执行当前所有的异步任务
,即从Event Queue
取出回调函数进入主线程执行。选择最先进入队列的
宏任务
(通常是 script 整体代码),如果有则执行。待其结束后,执行微任务队列
里面所有的微任务
检查是否有可执行的
宏任务
,有则执行。检查
微任务队列
中是否有可执行的微任务
,有则全部执行。再次检查是否有可执行的
宏任务
,有则执行,完成后,检查微任务队列
中是否有可执行的微任务
,有则全部执行……依次类推直到任务队列
为空。
在上述过程中,只有第 3 步到第 6 步的这个循环,被称为 Event Loop,即事件循环。
在事件循环中,每一次循环称为一次 tick
,每一次 tick
中需要执行的任务如下:
- 选择最先进入队列的
宏任务
(通常是 script 整体代码),如果有则执行; - 检查是否存在
微任务
,如果存在则不停的执行,直至清空微任务队列
; - 更新 render(每一次事件循环,浏览器都可能会去更新渲染);
如下图:
实例参考:事件循环实例
总结
事件循环是 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 进行许可。