一文梳理JavaScript 事件循环(Event Loop)
事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑。
众所周知,JS是单线程的,即同一时间只能运行一个任务。一般情况下这不会引发问题,但是如果我们有一个耗时较多的任务,我们必须等该任务执行完毕才能进入下一个任务,然而等待的这段时间常常让我们无法忍受,因为我们这段时间什么都不能做,包括页面也是锁死状态。
好在,时代在进步,浏览器向我们提供了JS引擎不具备的特性:Web API。Web API包括DOM API、定时器、HTTP请求等特性,可以帮助我们实现异步、非阻塞的行为。我们可以通过异步执行任务的方法来解决单线程的弊端,事件循环为此而生。
提问QAQ:为什么JavaScript是单线程的?
多个线程表示您可以同时独立执行程序的多个部分。确定一种语言是单线程还是多线程的最简单方法是看它拥有有多少个调用堆栈。JS 只有一个,所以它是单线程语言。
将JS设计为单线程是由其用途运行环境等因素决定的,作为浏览器脚本语言,JS的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。同时,单线程执行效率高。
1. Event Loop旧印象
大家熟悉的关于事件循环的机制说法大概是:主进程执行完了之后,每次从任务队列里取一个任务执行。如图所示,所有的任务分为同步任务和异步任务,同步任务直接进入任务队列-->主程序执行;异步任务则会挂起,等待其有返回值时进入任务队列从而被主程序执行。异步任务会通过任务队列的机制(先进先出的机制)来进行协调。具体如图所示:

同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们所熟悉的Event Loop (事件循环)。但是promise出现之后,这个说法就不太准确了。
2. Event Loop 后印象
2.1 理论
这里首先用一张图展示JavaScript的事件循环:

直接看这张图,可能黑人问号已经出现在同学的脑海。。。
这里将task分为两大类,分别是macroTask(宏任务)和microTask(微任务).一次事件循环:先运行macroTask队列中的一个,然后运行microTask队列中的所有任务。接着开始下一次循环(只是针对macroTask和microTask,一次完整的事件循环会比这个复杂的多)。
那什么是macroTask?什么是microTask呢?
JavaScript引擎把我们的所有任务分门别类,一部分归为macroTask,另外一部分归为microTack,下面是类别划分:
macroTask:
- setTimeout
 - setInterval
 - setImmediate
 - requestAnimationFrame
 - I/O
 - UI rendering
 
microTask:
- process.nextTick
 - Promise
 - Object.observe
 - MutationObserver
 
我们所熟悉的定时器就属于macroTask,仅仅了解macroTask的机制还是不够的。为直观感受两种队列的区别,下面上代码进行实践感知。
2.2 实践
以setTimeout、process.nextTick、promise为例直观感受下两种任务队列的运行方式。
console.log('main1');
process.nextTick(function() {
    console.log('process.nextTick1');
});
setTimeout(function() {
    console.log('setTimeout');
    process.nextTick(function() {
        console.log('process.nextTick2');
    });
}, 0);
new Promise(function(resolve, reject) {
    console.log('promise');
    resolve();
}).then(function() {
    console.log('promise then');
});
console.log('main2');
别着急看答案,先以上面的理论自己想想,运行结果会是啥?
最终结果是这样的:
main1
promise
main2
process.nextTick1
promise then
// 第二次事件循环
setTimeout
process.nextTick2
process.nextTick 和 promise then在 setTimeout 前面输出,已经证明了macroTask和microTask的执行顺序。但是有一点必须要指出的是。上面的图容易给人一个错觉,就是主进程的代码执行之后,会先调用macroTask,再调用microTask,这样在第一个循环里一定是macroTask在前,microTask在后。
但是最终的实践证明:在第一个循环里,process.nextTick1和promise then这两个microTask是在setTimeout这个macroTask里之前输出的,这是因为Promises/A+规范规定主进程的代码也属于macroTask。
主进程这个macroTask(也就是main1、promise和main2)执行完了,自然会去执行process.nextTick1和promise then这两个microTask。这是第一个循环。之后的setTimeout和process.nextTick2属于第二个循环
别看上面那段代码好像特别绕,把原理弄清楚了,都一样 ~
requestAnimationFrame、Object.observe(已废弃) 和 MutationObserver这三个任务的运行机制大家可以从上面看到,不同的只是具体用法不同。重点说下UI rendering。在HTML规范:event-loop-processing-model里叙述了一次事件循环的处理过程,在处理了macroTask和microTask之后,会进行一次Update the rendering,其中细节比较多,总的来说会进行一次UI的重新渲染。
3. 小结
总而言之,记住一次事件循环:先运行macroTask队列中的一个,然后运行microTask队列中的所有任务。接着开始下一次循环。
参考文献:
一文梳理JavaScript 事件循环(Event Loop)的更多相关文章
- JavaScript事件循环(Event Loop)机制
		
JavaScript 是单线程单并发语言 什么是单线程 主程序只有一个线程,即同一时间片断内其只能执行单个任务. 为什么选择单线程? JavaScript的主要用途是与用户互动,以及操作DOM.这决定 ...
 - JavaScript 事件循环 — event loop
		
引言 相信所有学过 JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 无法进行多线程编程,但是 JS 当中却有着无处不在的异步概念 .在初期许多人会把异步理解成类似多线程的编程模式 ...
 - 简单了解一下事件循环(Event Loop)
		
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
 - JS事件循环(Event Loop)机制
		
前言 众所周知,为了与浏览器进行交互,Javascript是一门非阻塞单线程脚本语言. 为何单线程? 因为如果在DOM操作中,有两个线程一个添加节点,一个删除节点,浏览器并不知道以哪个为准,所以只能选 ...
 - 事件循环Event loop到底是什么
		
摘要:本文通过结合官方文档MDN和其他博客深入解析浏览器的事件循环机制,而NodeJS有另一套事件循环机制,不在本文讨论范围中.process.nextTick和setImmediate是NodeJS ...
 - 事件循环 event loop 究竟是什么
		
事件循环 event loop 究竟是什么 一些概念 浏览器运行时是多进程,从任务管理器或者活动监视器上可以验证. 打开新标签页和增加一个插件都会增加一个进程,如下图:  浏览器渲染进程是多线程,包 ...
 - 浏览器与Node的事件循环(Event Loop)有何区别?
		
前言 本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的. 一.线程与进程 1. 概念 我们经常说 JS 是单线程执行的,指的是一个进程里 ...
 - JavaScript:彻底理解同步、异步和事件循环(Event Loop)  (转)
		
原文出处:https://segmentfault.com/a/1190000004322358 一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS ...
 - JavaScript:彻底理解同步、异步和事件循环(Event Loop)
		
一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个.不妨叫它主线程. 但是实际上还存在其他 ...
 
随机推荐
- ASP.NET Core on K8S学习之旅(14)Ingress灰度发布
			
本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章. 之前一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和 ...
 - 手机端页面访问PC页面自动跳手机端代码
			
<script> var mobileAgent = new Array("iphone", "ipod", "ipad", & ...
 - 创建执行线程方式三:实现Callable接口
			
Callable接口 ① Java 5.0 在 java.util.concurrent 提供了一个新的创建执行 线程的方式:Callable 接口② Callable 接口类似于 Runnable, ...
 - js 获取当前日期时间
			
function getCurrentDate(fulldate = false, separator = ['-', ':']) { let currentTimeObj = new Date(); ...
 - GitHub的raw.githubusercontent.com无法链接
			
今天在用Github下载是总报错,其中错误一开始是DNS的问题 查资料后得知是DNS污染,之后就先找真实的IP地址,添加进C:\Windows\System32\drivers\etc hosts文件 ...
 - 02 . Mysql基础操作及增删改查
			
SQL简介 SQL(Structured Query Language 即结构化查询语言) SQL语言主要用于存取数据.查询数据.更新数据和管理关系数据库系统,SQL语言由IBM开发. SQL语句四大 ...
 - STL中的迭代器分类
			
STL中迭代器的分类 五类迭代器如下: 1.输入迭代器:只读,一次传递 为输入迭代器预定义实现只有istream_iterator和istreambuf_iterator,用于从一个输入流i ...
 - Unity 游戏框架搭建 2019 (五十六/五十七) 需求分析-架构中最重要的一环&从 EmptyGO 到 Manager Of Managers
			
我们的项目开始立项的时候,最常见的一个情况就是:几个人的小团队,一开始什么也不做,就开始写代码,验证逻辑,游戏就开始写起来了.而公司的一些所谓的领导层面一开始就把游戏定义为我们要做一个大作.这个事情本 ...
 - Spring boot Sample 012之spring-boot-web-upload
			
一.环境 1.1.Idea 2020.1 1.2.JDK 1.8 二.目的 spring boot 整合web实现文件上传下载 三.步骤 3.1.点击File -> New Project -& ...
 - Java实现 LeetCode 766 托普利茨矩阵(暴力)
			
766. 托普利茨矩阵 如果一个矩阵的每一方向由左上到右下的对角线上具有相同元素,那么这个矩阵是托普利茨矩阵. 给定一个 M x N 的矩阵,当且仅当它是托普利茨矩阵时返回 True. 示例 1: 输 ...