一文告诉你 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 ...
随机推荐
- bootstrap 常用class(不定时更新)
1,字体居右 text-right 2,字体居中 text-center
- webpack快速入门——CSS分离与图片路径处理
1.在终端安装extract-text-webpack-plugin 2.引入插件 const extractTextPlugin = require("extract-text-webpa ...
- 摘要:ASP.NET的路由
原文:ASP.NET的路由系统:路由映射 对物理存在文件的路由 在成功注册路由的情况下,如果我们按照传统的方式访问一个物理文件(比如http://localhost:2738/Default.aspx ...
- 【Java并发编程】:使用wait/notify/notifyAll实现线程间通信
在java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...
- git使用时的一下简单命令
一.简介 SVN集中式版本控制系统 分布式版本控制系统 二.使用git 1.git init 将这个命令变成git可管理的仓库 2.git add filename 将这个文件添加到仓库 3.git ...
- echart 桑基图操作事项
例图 注意: option = { label:{//formatter名字 show:true, formatter:function(obj){ return obj.data.name+'_12 ...
- safari input默认样式
在i标签下嵌套一个input标签 设置了 -webkit-apprarence:none: 设置了宽高,和padding:3px 结果placeholder显示不全 经排查 这时候的input默认有 ...
- GBDT多分类示例
相当于每次都是用2分类,然后不停的训练,最后把所有的弱分类器来进行汇总 样本编号 花萼长度(cm) 花萼宽度(cm) 花瓣长度(cm) 花瓣宽度 花的种类 1 5.1 3.5 1.4 0.2 山鸢尾 ...
- Term Weighting
对文本分词后,接下来需要对分词后的每个term计算一个权重,重要的term应该给与更高的权重.举例来说,“什么产品对减肥帮助最大?”的term weighting结果可能是: “什么 0.1,产品 0 ...
- C/C++中的static
一.静态全局变量 理解static关键字之前首先回顾一下C/C++程序的在内存中的分配情况.从低地址到高地址依次分为:代码区.全局数据区.堆区.栈区.函数之外的全局变量和静态变量(包括全局变量和静态变 ...