Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >一篇文章搞懂浏览器Js事件循环机制

一篇文章搞懂浏览器Js事件循环机制

作者头像
努力的Greatiga
发布于 2022-07-25 02:22:08
发布于 2022-07-25 02:22:08
90400
代码可运行
举报
运行总次数:0
代码可运行

浏览器事件循环机制

前言

在初次入门学习和使用 JavaScript 的过程中,相信遇到过许多程序执行顺序及结果与预期不一致的问题,在查阅资料的过程中了解到原来是程序的执行有同步与异步之分;与此同时也会看到许多有关概念,例如回调函数、执行栈、任务队列、事件循环机制(Event Loop)、宏任务、微任务、Promise(ES6)等等。此时对于一个刚入门不久的小白来说,要理解消化这些概念真的不容易。对于入门不久的我来说也一样,所以写一篇博客记录一下,有关 JavaScript 的运行机制,以及上述的这些概念为什么会出现,又解决了什么问题。

一、JavaScript 是单线程

我们知道多线程是可以并行执行程序的,能提高程序运行效率。但是 JS 是一门单线程语言,同一时间内做一件事。

最初作为服务于浏览器的脚本语言,很多时候都是在与用户交互,这个过程涉及了许多 DOM 的操作,倘若使用多线程,那么就容易出现几个线程同时操作一个 DOM 的问题,那么浏览器此时要以哪一个线程为主呢?这样一来无疑增加了复杂性,所以 JS 成为了单线程。虽然说多线程处理起来也很高效,但对于当时直接服务于浏览器用户的 JS 来说,尽可能避免过度复杂,能更简单的处理相对好点吧。

二、异步任务及其回调函数

虽然单线程降低了复杂性,但是也有了新的问题。单线程是顺序执行程序,每一个任务要等待上一个任务执行完毕才执行,如果遇到执行时间太长或者出现了别的问题,那么就会一直卡在那,导致整个程序无法顺利执行完毕。为了解决问题,语言设计者希望在程序执行时,将一些耗时、有延迟的任务先挂起,让能快速执行完毕的任务先执行;按照这样的方式执行完整个程序后,在返回去执行那些被挂起的任务。因此有了同步任务与异步任务之分;在执行过程中,当前执行程序的线程称为主线程,同步任务直接在主线程立即执行,而那些异步任务,先给它挂在一边放着,等到主线程执行完了所有同步任务,再回来读取挂在一旁的异步任务,并且执行他们。

(1) 任务队列

任务队列是一系列事件组成的一个队列,也就是上面说到的异步任务挂起的地方。程序执行时会将定义的异步任务送入任务队列,或者用户点击鼠标触发的异步任务送入队列。等待主线程来执行它们。例如常见的各种事件(鼠标点击、键盘敲击、滚动等等)、又或者是 Ajax 那样等待响应的异步任务。

实际上,任务队列不止一种,因为处理的异步任务种类可能不同

(2) 回调函数 (callback)

回调函数往往就是异步任务所定义的代码。主线程执行完同步任务,就会回来开始读取任务队列中的异步任务并执行这些代码,同时也称为回调函数。

(3) 宏任务和微任务

异步任务又可以看为两种,通常由宿主环境(浏览器、node)提供的为宏任务,由语言标准提供的为微任务。 JavaScript 可能会在不同的宿主环境下运行,所以宏任务来自于宿主环境,而微任务作为语言标准,在任何环境下都可以使用。

常见宏任务
  • setTimeout
  • setInterval
  • setImmediate (仅 node 提供)
  • requestAnimationFrame (仅浏览器提供)
  • 各种交互 (鼠标点击、滚动等等)
  • I/O
常见微任务
  • Promise.then catch finally
  • MutationObserver (仅浏览器提供)
  • process.nextTick (仅 node 提供)

三、事件循环机制 (Event Loop)

主线程执行程序时会将定义的异步任务放入任务队列中,宏任务会放在宏任务队列,微任务放在微任务队列,当触发 UI 事件时,也会把相应任务放入队列。为了确保事件处理正常进行,主线程不阻塞。所以有了解决方案 Event Loop,事件循环线程是独立于主线程的,并且一直存在直到整个脚本环境被关闭。无论是主线程执行时添加的异步任务,还是 UI 交互触发后添加的异步任务,事件循环机制都会按一定规则循环读取并且执行。

那么该循环机制如何运行呢?

  • (1) 打开某个宿主环境时,主线程执行同步任务的所有代码,形成一个执行栈;把遇到的异步任务放入相应的队列里;同时一个独立于主线程的事件循环线程也被创建并一直存在。
  • (2) 当主线程执行完同步任务,会将该执行过程中添加的微任务全部执行完,之后由事件循环机制协调。
  • (3) 事件循环读取当前宏任务队列的一个宏任务,并放入执行栈中执行
  • (4) 在执行过程中遇到宏任务和微任务,按照相同的方式放入相应队列
  • (5) 该宏任务执行完毕后立即执行此次宏任务中所添加的所有微任务
  • (6) 回到第 (3) 步开始重复后面步骤。
  • 说那么多,看个例子
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
console.log('1-1');

Promise.resolve().then(() => console.log('微任务 1-1'));

new Promise((resolve) => {
  console.log('1-2');
  resolve();
}).then(() => {
  console.log('微任务 1-2')
});

setTimeout(() => console.log('宏任务 1-1'), 100);

console.log('1-3');
//1-1
//1-2
//1-3
//微任务 1-1
//微任务 1-2
//宏任务 1-1
  • 主线程开始执行,形成一个执行栈
  • 碰到第一个 console.log('1-1'),并打印 -> 1-1
  • 碰到第一个 Promise,已为成功状态,将其 then() 加到微任务中
  • 碰到第二个 Promise,先执行其中的 console.log('1-2'),打印 -> 1-2,并将其 then() 放入微任务队列
  • 碰到第一个宏任务,放入宏任务队列
  • 碰到 console.log('1-3'),打印 -> 1-3
  • 主线程执行完所有同步任务,开始执行本次添加的所有微任务
  • 读取微任务队列
  • 遇到先进去的第一个 then() ,打印 -> 微任务 1-1
  • 遇到后进去的 then() 打印 -> 微任务 1-2
  • 本次主线程任务完成,下面由事件循环机制来协调。开始读取宏任务队列
  • 遇到第一个放入的宏任务 setTimeout(),将其丢到执行栈延时 100ms 执行,打印 -> 宏任务 1-1
  • 第一次宏任务执行完毕,读取微任务队列,发现没有微任务。进入第二次循环
  • 读取宏任务队列,发现没有宏任务。JS 执行栈开始摸鱼...

到这里其实会发现,微任务都会紧跟在当前执行栈执行同步任务后执行,而存好的宏任务被放在下次执行,好似重新开始一样。

按个人总结来就是(不一定对),主线程的执行栈是专门用来执行代码的;当事件循环线程读取到一个宏任务时,将其放入执行栈执行,主线程会执行其中定义的同步任务,将遇到的宏任务和微任务存起来,在本次同步任务执行完之后立即执行微任务。而此次存好的宏任务又会按照相同的方式在下一次循环中进行。因为事件循环机制一次循环只读取执行一个宏任务。

由此看来其实整个程序也可以看成是一个宏任务,而首次添加的宏任务和微任务是按照上面的方式一层层刨开,按照一次执行一个宏任务和里面所有微任务的规则进行

  • 再看个例子说明宏任务是一次循环读取一次,并且会执行宏任务下所有微任务
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
console.log('开始执行主线程');
console.log('0-1');

Promise.resolve().then(() => console.log('微任务 0-1\n-----'));

setTimeout(() => {//宏任务 1
  console.log('第一个宏任务');
  console.log('宏任务 1-1');
  Promise.resolve().then(() => console.log('微任务 1-1'));
  Promise.resolve().then(() => console.log('微任务 1-2\n-----'));

  setTimeout(() => {//宏任务3
    console.log('第三个宏任务');
    console.log('宏任务 3-1')
    Promise.resolve().then(() => console.log('微任务 3-1\n-----'))
  },10);

},100);

setTimeout(() => {//宏任务2
  console.log('第二个宏任务');
  console.log('宏任务2-1');
  Promise.resolve().then(() => console.log('微任务 2-1\n-----'));
},100);

console.log('0-2');

***************************

执行结果

开始执行主线程
0-1
0-2
微任务 0-1
-----
第一个宏任务
宏任务 1-1
微任务 1-1
微任务 1-2
-----
第二个宏任务
宏任务 2-1
微任务 2-1
-----
第三个宏任务
宏任务 3-1
微任务 3-1
-----
  • 开始执行主线程后,将 微任务 0-1 、 宏任务1 、 宏任务2 存入队列,并先打印其同步任务代码,又打印微任务代码
  • 开始第一次事件循环,读取宏任务1(第一个定时),将 微任务 1-1 、微任务 1-2、和宏任务3 存入队列。打印方式如上一条。
  • 开始第二次事件循环,读取宏任务2(第二个定时),将 微任务 2-1 存入队列,打印方式如上。
  • 开始第三次事件循环,读取宏任务队列中最后一个进去的宏任务3(宏任务1中定义的定时器),将 微任务 3-1 存入队列,打印方式如上。

大概流程图

提示,虽然说是一次循环只读取一个宏任务,但是他没说要等当前宏任务执行完才进行下一次循环哦!!,事件循环读取到队列中的任务并且让它开始执行后,就可以开始下次循环,不需要等待

  • 下面改动的例子,留给自己做练习吧
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
console.log(1);

Promise.resolve().then(() => console.log(2));

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

  setTimeout(() => {
    console.log(5);
  },10);

},200);

setTimeout(() => {
  console.log(6);
  Promise.resolve().then(() => console.log(7));
  setTimeout(() => {
    console.log(8)
  }, 300);
},100);

console.log(9);

自己在纸上写了一下,将代码在浏览器上运行之后对比,发现完全正确。你也可以自己写一下哦。

2020/9/22 更新

有一种情况,那就是 then() 之后接着 then() ,那么此时的顺序呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
console.log('1-1');

Promise.resolve().then(() => console.log('微任务 1-1')).then(() => console.log('微任务 1-3'));

new Promise((resolve) => {
  console.log('1-2');
  resolve();
}).then(() => {
  console.log('微任务 1-2')
}).then(() => console.log('微任务 1-4'));

setTimeout(() => console.log('宏任务 1-1'), 100);

//1-1
//1-2
//1-3
//微任务 1-1
//微任务 1-2
//微任务 1-3
//微任务 1-4
//宏任务 1-1

可以看到他的运行顺序,说明在 then() 执行之后,如果后面还接着 then() 那么按照同样的方式添加到微任务队列,等到之前添加的第一层 then() 都执行完后,在到微任务队列里面读取后面添加的 then(),运行方式如上。并且只有当微任务队列为空时,事件循环机制才会进行到下一轮并读取新的宏任务。

参考链接

阮一峰的网络日志 JavaScript 运行机制详解:再谈Event Loop

知乎作者:tigerHee js中的宏任务与微任务

博客园作者:daisy,gogogo JavaScipt 中的事件循环 event loop,以及微任务和宏任务的概念

国外作者写的一篇文章 Tasks, microtasks, queues and schedules

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
「硬核JS」一次搞懂JS运行机制
从开始做前端到目前为止,陆续看了很多帖子讲JS运行机制,看过不久就忘了,还是自己理一遍好些
isboyjc
2022/03/28
2K0
「硬核JS」一次搞懂JS运行机制
10分钟了解JS堆、栈以及事件循环的概念
其实一开始对栈、堆的概念特别模糊,只知道好像跟内存有关,又好像事件循环也沾一点边。面试薄荷的时候,面试官正好也问到了这个问题,当时只能大方的承认不会。痛定思痛,回去好好的研究一番。 我们将从JS的内存机制以及事件机制和大量的🌰(例子)来了解栈、堆究竟是个什么玩意。概念比较多,不用死读,所有的🌰心里想一遍,浏览器console看一遍就很清楚了。 let's go
用户2356368
2024/02/05
1.3K0
10分钟了解JS堆、栈以及事件循环的概念
一文带你搞懂浏览器的事件循环机制!
Event Loop 也叫做“事件循环”,它其实与 JavaScript 的运行机制有关,乍一看云里雾里,不用着急,读完本文你便会知晓它的含义,这一切都要从 JavaScript 的初始设计说起。
用户6297767
2023/11/21
9820
一文带你搞懂浏览器的事件循环机制!
# 一次搞懂 EventLoop
众所周知,JS 是一门单线程语言,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?
九旬
2024/04/03
1180
JS 事件循环
众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?
Umbrella1024
2021/03/25
15.8K0
【前端进阶】深入浅出浏览器事件循环【内附练习题】
我们看一个很经典的图,这张图基本可以概括了事件循环(该图来自演讲—— 菲利普·罗伯茨:到底什么是Event Loop呢?| 欧洲 JSConf 2014[1])后面演示用的 Loupe[2] 也是该演讲者写的((Loupe是一种可视化工具,可以帮助您了解JavaScript的调用堆栈/事件循环/回调队列如何相互影响))
GopalFeng
2020/10/27
1.1K0
【前端进阶】深入浅出浏览器事件循环【内附练习题】
JS:事件循环机制(Event Loops)
* 为什么 `setTimeout()` 设定的时间是 0 毫秒,但 1 却是在最后输出的?
前端小tips
2021/12/08
2.2K0
JS:事件循环机制(Event Loops)
JS运行机制
本文阐述了浏览器端和node端的js运行机制执行的过程,还进行了两者的运行机制比较,以及同步任务和异步任务的说明,两种异步任务的必要性,以及各自有哪些回调,部分回调的优先级。
ClyingDeng
2022/11/29
3.9K0
JS运行机制
Js异步机制的实现
JavaScript是一门单线程语言,所谓单线程,就是指一次只能完成一件任务,如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯,坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应也就是假死状态,往往就是因为某一段Javascript代码长时间运行比如死循环,导致整个页面卡在这个地方,其他任务无法执行。
WindRunnerMax
2020/08/27
2.9K0
事件循环详解
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
Careteen
2022/02/14
6460
javascript事件循环
JavaScript 从一开始被创造出来就使用的单线程,这主要与他的用途相关。JavaScript主要用来与用户交互、操作网页上的dom元素等工作。
腾讯IVWEB团队
2020/06/29
1.3K0
javascript事件循环
Js 的事件循环(Event Loop)机制以及实例讲解
大家都知道js是单线程的脚本语言,在同一时间,只能做同一件事,为了协调事件、用户交互、脚本、UI渲染和网络处理等行为,防止主线程阻塞,Event Loop方案应运而生…
OBKoro1
2020/10/27
1.7K0
Js 的事件循环(Event Loop)机制以及实例讲解
js运行机制同步与异步(宏任务与微任务)
众所周知,javascript的最大特点就是单线程,同一时间追能做同一件事,所以为了防止主线程的阻塞,在代码执行时分为同步任务和异步任务,所有的同步任务在主线程上执行,形成执行栈,而异步任务形成一个新的任务队列,又把任务队列中的异步任务分为宏任务和微任务,虽然他们都在任务队列中,但是它们却在不同的队列中,微任务的执行优先级大于宏任务,他们的结构如图所示。
qiangzai
2022/09/26
1.2K0
js运行机制同步与异步(宏任务与微任务)
事件循环Event Loop
js是单线程,js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:1)同步任务 2)异步任务
程序员法医
2022/08/11
1.1K0
事件循环Event Loop
新生代总结 JavaScript 运行机制解析
这些虽然看起来很深奥很复杂,但是如果你了解了 JavaScript 的运行机制,这些问题都能够一一化解
小丞同学
2021/08/23
4150
Javascript运行机制(Event loop)原理知道吗?不懂就来看看吧,一篇文章让你搞定
在写这篇文章之前,我看了很多写的不错的文章,但是每篇文章都有那么几个关键的点,很多篇文章凑在一起综合来看,才可以对这些概念有较为深入的理解。所以,我就想要写这么一篇文章,结合自己的理解以及示例代码,用最通俗的文字表达出来。
吴佳
2022/09/26
5560
JavaScript事件循环机制解析
最近面试了很多家公司,这道题几乎是必被问到的一道题。之前总觉得自己了解得差不多,但是当第一次被问到的时候,却不知道该从哪里开始说起,涉及到的知识点很多。于是花时间整理了一下。并不仅仅是因为面试遇到了,而是理解JavaScript事件循环机制会让我们平常遇到的疑惑也得到解答。
前端迷
2020/07/16
6940
JavaScript事件循环机制解析
10分钟了解JS堆、栈以及事件循环的概念
前言 其实一开始对栈、堆的概念特别模糊,只知道好像跟内存有关,又好像事件循环也沾一点边。面试薄荷的时候,面试官正好也问到了这个问题,当时只能大方的承认不会。痛定思痛,回去好好的研究一番。 我们将从JS
用户2356368
2019/04/03
1.4K0
10分钟了解JS堆、栈以及事件循环的概念
JS中的事件循环机制与宏队列、微队列笔记
为什么JavaScript是一门单线程语言?作为一门浏览器脚本语言,它的主要用途就是操作DOM和与用户交互设计,如果说js是多线程的话,那么它在操作DOM的时候,一个线程对DOM进行了新增操作,另一个线程对DOM进行了删除操作,那么这个时候js的处理将会变得十分复杂。为了避免这种情况,JavaScript一诞生就是单线程。
帅的一麻皮
2020/07/20
2.1K0
JS中的事件循环机制与宏队列、微队列笔记
JS 事件循环、微任务和宏任务
JS 是单线程执行的,所有 JS 代码都要放在主线程中运行。 如果把异步 IO 等耗时较长的任务也放在主线程中处理,会阻塞后续同步代码的执行,造成卡顿等现象。因此,浏览器等运行环境额外设置了异步处理线程,专门用于处理异步事件。
lonelydawn
2021/09/26
1.4K0
相关推荐
「硬核JS」一次搞懂JS运行机制
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验