一文告诉你 Event Loop 是什么?
Event Loop 也叫做“事件循环”,它其实与 JavaScript 的运行机制有关。
JS初始设计
JavaScript 在设计之初便是单线程,程序运行时,只有一个线程存在,在特定的时候只能有特定的代码被执行。这和 JavaScript 的用途有关,它是一门浏览器脚本语言,通常是用来操作 DOM 的,如果是多线程,一个线程进行了删除 DOM 操作,另一个添加 DOM,此时该如何处理?所以 JavaScript 在设计之初便是单线程的。
虽然 HTML5 增加了 Web Work 可用来另开一个线程,但是该线程仍受主线程的控制,所以 JavaScript 的本质依然是单线程
线程和进程
进程和线程是操作系统中的概念,在操作系统中,一个任务就是一个进程,比如你在电脑上打开了一个浏览器来观看视频,便是打开了一个浏览器进程,此时又想记录视频中的重要信息,于是你打开了备忘录,这便是一个备忘录进程,系统会为每个进程分配它所需要的地址空间,数据,代码等系统资源。如果把一个进程看做一个小的车间,车间里有很多工人,有的负责操作机器,有的负责搬运材料,每个工人可以看做一个线程,线程可以共享进程的资源。可以说,线程是进程的最小单位,一个进程可以包含多个线程。
执行栈和任务队列
单线程的 JavaScript 一段一段地执行,前面的执行完了,再执行后面的,试想一个,如果前一个任务需要执行很久,比如接口请求、I/O 操作,此时后面的任务只能干巴巴地等待么?干等不仅浪费了资源,而且页面的交互程度也很差。JavaScript 意识到了这个问题,他们将任务分成了同步任务和异步任务,对于二者有不同的处理。
JavaScript 在运行时会将变量存放在堆(heap)和栈(stack)中,堆中通常存放着一些对象,而变量及对象的指针则存放在栈中。JavaScript 在执行时,同步任务会排好队,在主线程上按照顺序执行,前面的执行完了再执行后面的,排队的地方叫执行栈(execution context stack)。JavaScript 对异步任务不会停下来等待,而是将其挂起,继续执行执行栈中的同步任务,当异步任务有返回结果时,异步任务会加入与执行栈不一样的队列,即任务队列(task queue),所以任务队列中存放的是异步任务执行完成后的结果,通常是回调函数。
当执行栈的同步任务已经执行完成,此时主线程闲下来,它便会去查看任务队列是否有任务,如果有,主线程会将最先进入任务队列的任务加入到执行栈中执行,执行栈中的任务执行完了之后,主线程便又去任务队列中查看是否有任务可执行。主线程去任务队列读取任务到执行栈中去执行,这个过程是循环往复的,这便是 Event Loop,事件循环。
网上有张流传甚广的图对这一过程进行了总结,在图中我们可以看到,JavaScript 在运行时产生了堆和栈,ajax、setTimeout 等异步任务被挂起,异步任务的返回结果加入任务队列,主线程会循环往复地读取任务队列中的任务,加入执行栈中执行。

(JavaScript 运行机制,图片来源于网络)
宏任务与微任务
异步任务有更深一层的划分,它们是宏任务(macro task)和微任务(micro task),二者的执行顺序也有差别。在上面我们讲到异步任务的结果会进入任务队列中,对于不同的事件类型,宏任务会加入宏任务队列,微任务会加入微任务队列。在执行栈中的同步任务执行完成后,主线程会先查看任务队列中的微任务,如果有没有,则去宏任务队列中取出最前面的一个事件加入执行栈中执行;如果有,则将所有在微任务队列中的事件依次加入执行栈中执行,直到所有事件执行完成后,再去宏任务中取出最前面的一个事件加入执行栈,如此循环往复。
由此我们可以得出结论,主线程总是会先查看微任务队列,等到微任务队列中的事件都处理完成后,再去宏任务队列中添加一个事件到任务栈中执行。
常见的宏任务有 setTimeout,setInterval;常见的微任务有 new Promise。
代码例子体验
console.log(1)
setTimeout(function() {
console.log(2)
new Promise(function(resolve) {
console.log(3)
resolve(4)
}).then(function(num) {
console.log(num)
})
}, 300)
new Promise(function(resolve) {
console.log(5)
resolve(6)
}).then(function(num) {
console.log(num)
})
setTimeout(function() {
console.log(7)
}, 400)
我们一步步来分析上面的执行顺序,当程序开始执行时,首先打印出 1,然后遇到了 setTimeout,主程序将它挂起,300 毫秒后它的回调函数进入宏任务队列,我们记做 setTimeout1。随后遇到了 new Promise,resolve 部分是同步执行的,所以会打印出 5,then 中的回调函数进入微任务队列,我们暂时记做 promise1。最后是 setTimeout,同理在 400 毫秒后加入了宏任务队列,我们记做 setTimeout2。
此时任务队列的情况如下:
|
宏任务 |
微任务 |
|
setTimeout1 |
promise1 |
|
setTimeout2 |
执行栈中的同步任务执行完成后,主线程查看任务队列时发现存在微任务,于是把 promise1 执行了,打印出 6。此时微任务队列已经空了,于是开始查看宏任务队列,将 setTimeout1 的回调函数加入任务栈开始执行,于是首先打印出 2,之后是 3,再将 then 中的回调函数加入微任务队列,我们记做 promise2。
此时任务队列的情况如下:
|
宏任务 |
微任务 |
|
setTimeout2 |
promise2 |
此时执行栈也空了,于是将微任务 promise2 加入执行栈,打印出 4。此时微任务已经执行完,再查看宏任务队列,于是执行 setTimeout2,打印出 7。
所以代码中的输出顺序是 1,5,6,2,3,4,7。
注意,主线程对微任务的读取是逐个读取,直到微任务队列为空,再读取宏队列,对宏任务队列的读取在一个循环中只读取一个。
小结
我们了解了 JavaScript 的运行机制,它是单线程的。JavaScript 中的任务可分为同步任务和异步任务,同步任务总是先进入执行栈中执行,异步任务会被挂起,直到有结果返回时,异步任务会进入任务队列中等待主线程读取执行。当执行栈为空时,主线程便会循环往复地读取任务队列中的事件,进入执行栈执行,这个过程叫 Event Loop。主线程对任务队列的读取也有先后之分,首先会去查找微任务,微任务队列的事件都执行完毕后,再读取最前面的宏任务进行执行,执行完再读取微任务队列,这个过程也是循环往复的。
一文告诉你 Event Loop 是什么?的更多相关文章
- 用大白话告诉你什么是Event Loop
文章原文地址 前沿 从前有座山,山里有座庙,庙里有个小和尚在讲故事.讲什么呢?讲的是: 从前有座山,山里有座庙,庙里有个小和尚在讲故事.讲什么呢?讲的是: 从前有座山,山里有座庙,庙里有个小和尚在讲故 ...
- 一文梳理JavaScript 事件循环(Event Loop)
事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑. 众所周知,JS是单线程的,即同一时间只能运行一个任务.一般情况下这不会引发问题,但是如果我们有一个 ...
- [转载]JavaScript 运行机制详解:再谈Event Loop
https://app.yinxiang.com/shard/s8/sh/b72fe246-a89d-434b-85f0-a36420849b84/59bad790bdcf6b0a66b8b93d5e ...
- 【朴灵评注】JavaScript 运行机制详解:再谈Event Loop
PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:“看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲一下就 ...
- [译]Node.js - Event Loop
介绍 在读这篇博客之前,我强列建议先阅读我的前两篇文章: Getting Started With Node.js Node.js - Modules 在这篇文章中,我们将学习 Node.js 中的事 ...
- JavaScript 运行机制详解:再谈Event Loop
原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...
- JavaScript 运行机制详解:深入理解Event Loop
Philip Roberts的演讲<Help, I'm stuck in an event-loop>,详细.完整.正确地描述JavaScript引擎的内部运行机制. 一.为什么JavaS ...
- 对Node.JS的事件轮询(Event Loop)的理解
title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...
- javascript运行机制详解: 再谈Event Loop(转)
作者: 阮一峰 日期: 2014年10月 8日 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts ...
随机推荐
- about BFC
https://www.cnblogs.com/lhb25/p/inside-block-formatting-ontext.html Box.Formatting Context(BFC)
- Elasticsearch-搜索推荐
对于es中搜索推荐的功能的实现: 1.使用match_phrase_prefix来实现search-time搜索推荐,原理根match_phrase类似,唯一的区别是把最后一个term作为前缀去搜索, ...
- 恢复xfs文件系统superblock实验
1. 创建一个XFS文件系统[root@localhost ~]# mkfs.xfs -f /dev/vdb1meta-data=/dev/vdb1 isize=256 ...
- Android UiAutomator UiDevice API
UiDevice为单例模式 1.获取设备 static UiDevice getInstance() This method is deprecated. Should use getInstance ...
- J05-Java IO流总结五 《 BufferedInputStream和BufferedOutputStream 》
1. 概念简介 BufferedInputStream和BufferedOutputStream是带缓冲区的字节输入输出处理流.它们本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流 ...
- java语言编程入门
1 概述 1.1 计算机 计算机包括硬件和软件两部分.硬件包括计算机中可以看得见的物理部分.软件提供看不见的指令.这些指令控制硬件并且使得硬件完成特定的任务. 1.2 程序设计 定义:创建(或开发软件 ...
- DataTable自身查询方法
这里说到的查询有两种. 1.DataTable.Select 2.DataTable.Rows.Find a>先建立一个DataTable供使用吧. /// <summary> ...
- java文件上传-原始的Servlet方式
前言: 干了这几个项目,也做过几次文件上传下载,要么是copy项目以前的代码,要么是百度的,虽然做出来了,但学习一下原理弄透彻还是很有必要的.刚出去转了一圈看周围有没有租房的,在北京出去找房子是心里感 ...
- 泛型深入--java泛型的继承和实现、泛型擦除
泛型实现类: package generic; /** * 泛型父类:子类为“富二代”:子类的泛型要比父类多 * 1,保留父类的泛型-->子类为泛型类 * 2,不保留父类泛型-->子类按需 ...
- JVM-垃圾收集算法、垃圾收集器、内存分配和收集策略
对象已死么? 判断一个对象是否存活一般有两种方式: 1.引用计数算法:每个对象都有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1.计数为0时可以回收. 2.可达性分析算法(Reachab ...