深入理解 Event Loop
众所周知,JavaScript(以下简称 JS) 是单线程语言,在 html5 中增加了 web workers,web workers 是新开了线程执行的,那么 JS 还是单线程的吗?当然是,为什么要设计成单线程?
网上有很多说法,大部分都说是多个线程同时对一个dom操作(同时修改dom内容,一个线程增加属性,一个线程删除属性),会非常混乱,当然如果支持多线程就会相应的就要加入多线程的锁机制,那么 JS 就变得非常复杂了,想想 JS 最开始设计的初衷就是用于用户交互,而且当时的原始需求是:功能不需要太强,语法较为简单,容易学习和部署,Brendan Eich 只用了10天,就设计完成了这种语言的第一版,因此也不可能加入多线程这么复杂的技术。
即使现在支持 web workers,由于没有多线程的机制,web workers 和执行线程只能通过 postMessage 来通信,而且由于没有锁,web workers 无法访问 window 和 document 对象。
JS 的单线程是指一个浏览器进程中只有一个 JS 的执行线程,即同一时刻内只会有一段代码在执行。
Micro-Task 与 Macro-Task
单线程如何实现异步?JS 设计了一个事件循环的方式。所有的代码执行均按照事件循环的方式进行。
事件循环中分两种任务:一个是宏任务(Macro-Task),另一个是微任务(Micro-Task)。常见的宏任务和微任务如下。
宏任务:script(整体代码)、setTimeout、setInterval、requestAnimationFrame、I/O、事件、MessageChannel、setImmediate (Node.js) 微任务:Promise.then、 MutaionObserver、process.nextTick (Node.js)
事件循环按下图的方式进行。
注意: 宏任务执行完后,需要清空当前微任务队列后才回去执行下一个宏任务,如果微任务里面产生了新的微任务,仍然会在当前事件循环里面被执行完,后面会举例说明。
来个示例验证下上面的流程。
<script>console.log(1);setTimeout(function timeout1() {console.log(2);}, 0);Promise.resolve().then(function promise1() {console.log(3);setTimeout(function timeout2() {console.log(4);Promise.resolve().then(function promise2() {console.log(5);});}, 0);return Promise.resolve().then(function promise3() {console.log(6);return Promise.resolve().then(function promise4() {console.log(7);});}).then(function promise5() {console.log(8);});})console.log(9);
</script><script>console.log(10);setTimeout(function timeout3() {console.log(11);}, 0);Promise.resolve().then(function promise6() {console.log(12);});
</script>
复制代码
按照上面流程梳理下执行流程:
- 将两个宏任务(两个script代码)初始化进宏任务队列,宏任务队列为:
[script1, script2]
- script1 出队压入执行栈执行,宏任务队列为:
[script2]
- 同步代码执行输出:1,
- 0ms 后把 timeout1 放入宏任务队列,宏任务队列为:
[script2, timeout1]
- promise1 入队,微任务队列为:
[promise1]
- 同步代码执行输出:9
- script1 执行完毕,进入微任务执行阶段,promise1 出队压入执行栈执行,微任务队列为空
- 同步代码执行输出:3
- 0ms 后把 timeout2 放入宏任务队列,宏任务队列为:
[script2, timeout1, timeout2]
- promise3 入队,微任务队列为:
[promise3]
- promise1 执行完毕,继续判断微任务队列是否为空,promise3 出队压入执行栈执行,微任务队列为空
- 同步代码执行输出:6
- promise4 入队,微任务队列为:
[promise4]
- promise3 执行完毕,promise5 入队,微任务队列为:
[promise4,promise5]
- 判断微任务队列是否为空,promise4 出队压入执行栈执行,微任务队列为:
[promise5]
- 同步代码执行输出:7
- promise4 执行完毕,继续判断微任务队列是否为空,promise5 出队压入执行栈执行,微任务队列为空
- 同步代码执行输出:8
- 微任务队列清空,宏任务 script2 出队压入执行栈执行,宏任务队列为空
- 同步代码执行输出:10
- 0ms 后把 timeout3 放入宏任务队列,宏任务队列为:
[timeout1, timeout2, timeout3]
- promise6 入队,微任务队列为:
[promise6]
- script2 执行完毕,进入微任务执行阶段,promise6 出队压入执行栈执行,微任务队列为空
- 同步代码执行输出:12
- 微任务队列为空,宏任务 timeout1 压入执行栈执行,宏任务队列为
[timeout2, timeout3]
- 同步代码执行输出:2
- timeout1执行完毕,微任务队列为空,宏任务 timeout2 压入执行栈执行,宏任务队列为
[timeout3]
- 同步代码执行输出:4,promise2 入队,微任务队列为:
[promise2]
- timeout2 执行完毕,判断微任务队列是否为空,promise2 出队压入执行栈执行,微任务队列为空
- 同步代码执行输出:5
- promise2执行完,微任务队列为空,宏任务 timeout2 压入执行栈执行,宏任务队列为空
- 同步代码执行输出:11
- timeout3执行完毕,微任务队列为空,宏任务队列为空
setTimeout
setTimeout 的 delay 最小值在不同浏览器的有差异,在 Chrome 74 上测试的结果是 2ms,Firefox 67 上测试的记过是 1ms。
最小值是什么意思?就是小于这个值后,浏览器按照0处理。比如在 Chrome 上,测试下面的代码:
setTimeout(function(){console.log(1)},1.99);
setTimeout(function(){console.log(2)},0);
复制代码
输出的结果为 1、2,而
setTimeout(function(){console.log(1)},2);
setTimeout(function(){console.log(2)},0);
复制代码
输出的结果为 2、1,说明 2ms 是有效的。
另外 setTimeout 是从调用开始计时,到了时间就放入宏任务队列,我们来看下面的例子。
var s = Date.now()
setTimeout(function timeout1() {console.log(1)
}, 200)while (Date.now() - s <= 200) {
}setTimeout(function timeout2() {console.log(2)
}, 0)
复制代码
- timeout1 200ms 后会放入到宏任务队列中
- while 执行了 200ms,此时 timeout1 已经先添加到宏任务队列中,因此最终打印结果为:1、2
- 如果将 while 的时间设置小于 200ms,考虑到代码执行需要花费时间,将 while 的条件改为
Date.now() - s <= 198
- 测试 while 执行只花费了 198ms,timeout2 会被先添加到宏任务队列中,因此最终打印结果会是:2、1
setInterval
和 setTimeout 相同,调用开始计时,按 delay 时间将回调添加到宏任务队列中。那么 setInterval 是按 delay 不断的向宏任务队列添加任务,还是需要等待已添加的任务执行完后再添加,还是其他机制?
思考下面代码:
var start = Date.now()var id = setInterval(function interval() {var whileStart = Date.now() console.log(whileStart - start) // 输出 interval1 调用的时间和最开始调用计时的时间差,即过了多久才调用while (Date.now() - whileStart < 250) { // 相当于 sleep 250ms}
}, 100)setTimeout(function timeout() {clearInterval(id)console.log(Date.now() - start)
}, 400)
复制代码
打印的时间间隔是?
100
351
605
855
复制代码
为了更好的理解,用图示来解释上面的流程。
参考
JavaScript语言的历史
深入理解 Event Loop相关推荐
- JavaScript中事件循环的理解 Event Loop
为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop) 事件循环(Event Loop) 在JavaScript中,所有的任务都 ...
- 【js进阶】全面理解Event Loop这一篇就够了
文章目录 一.前言 二.Event Loop知识铺垫 1.微任务(MircoTask) 2.宏任务(MacroTask/Task) 3.javascript runtime 三.浏览器环境的Event ...
- 跟着 Event loop 规范理解浏览器中的异步机制
原文发自我的 GitHub blog,欢迎关注 前言 我们都知道 JavaScript 是一门单线程语言,这意味着同一事件只能执行一个任务,结束了才能去执行下一个.如果前面的任务没有执行完,后面的任务 ...
- 理解node.js中的 Event Loop
node中的 "event loop" 正是node能处理高并发的核心所在.也正是因为它,node虽然在本质上是个单线程,却能让大量的操作处于后台运行.这篇文章将详细说明 even ...
- JavaScript 运行机制详解:Event Loop
转自: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单 ...
- 【朴灵评注】JavaScript 运行机制详解:再谈Event Loop
PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:"看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲 ...
- JavaScript 运行机制详解:再谈Event Loop
原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...
- Js Event Loop
Js Event Loop 本文主要介绍 Node 中的事件循环 参考 官网 (什么是 Event Loop? - 阮一峰的网络日志 (ruanyifeng.com)) What is the Eve ...
- Event Loop事件循环机制
转载自:阮一峰博客<JavaScript 运行机制详解:再谈Event Loop> 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个 ...
最新文章
- 下载MySQL数据库
- android 定义集合长度,Android Dex文件结构解析
- navcat设置oracle表主键自增_初识 Oracle 表空间设置与管理
- VC++ 模拟quot;CLICK事件quot;关闭指定窗体
- ICCV 2019 | 基于关联语义注意力模型的图像修复
- 【设计模式系列】行为型之策略模式
- 标识符怎么读_音标怎么学?到底该学英式还是美式
- java喷泉编码_Java干货分享使用JS实现简单喷泉效果
- Java8 函数式编程之函数接口(上)
- python3 验证用户名密码
- Python监控屏幕并截图保存
- Unable to process Jar entry
- Luogu5788 【模板】单调栈
- 解决Win10系统关机自动重启问题
- 阿里巴巴JAVA开发手册最新版pdf
- Air720x(3229)系列4G模块设计手册
- python实现游程编码(leetcode)
- 天河二号超级计算机能买到吗,天河二号计算机是巨型机吗
- Druid数据库连接池监控的使用
- Codeforces E. Game With String