众所周知,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>
复制代码

按照上面流程梳理下执行流程:

  1. 将两个宏任务(两个script代码)初始化进宏任务队列,宏任务队列为:[script1, script2]
  2. script1 出队压入执行栈执行,宏任务队列为:[script2]
  3. 同步代码执行输出:1,
  4. 0ms 后把 timeout1 放入宏任务队列,宏任务队列为:[script2, timeout1]
  5. promise1 入队,微任务队列为:[promise1]
  6. 同步代码执行输出:9
  7. script1 执行完毕,进入微任务执行阶段,promise1 出队压入执行栈执行,微任务队列为空
  8. 同步代码执行输出:3
  9. 0ms 后把 timeout2 放入宏任务队列,宏任务队列为:[script2, timeout1, timeout2]
  10. promise3 入队,微任务队列为:[promise3]
  11. promise1 执行完毕,继续判断微任务队列是否为空,promise3 出队压入执行栈执行,微任务队列为空
  12. 同步代码执行输出:6
  13. promise4 入队,微任务队列为:[promise4]
  14. promise3 执行完毕,promise5 入队,微任务队列为:[promise4,promise5]
  15. 判断微任务队列是否为空,promise4 出队压入执行栈执行,微任务队列为:[promise5]
  16. 同步代码执行输出:7
  17. promise4 执行完毕,继续判断微任务队列是否为空,promise5 出队压入执行栈执行,微任务队列为空
  18. 同步代码执行输出:8
  19. 微任务队列清空,宏任务 script2 出队压入执行栈执行,宏任务队列为空
  20. 同步代码执行输出:10
  21. 0ms 后把 timeout3 放入宏任务队列,宏任务队列为:[timeout1, timeout2, timeout3]
  22. promise6 入队,微任务队列为:[promise6]
  23. script2 执行完毕,进入微任务执行阶段,promise6 出队压入执行栈执行,微任务队列为空
  24. 同步代码执行输出:12
  25. 微任务队列为空,宏任务 timeout1 压入执行栈执行,宏任务队列为[timeout2, timeout3]
  26. 同步代码执行输出:2
  27. timeout1执行完毕,微任务队列为空,宏任务 timeout2 压入执行栈执行,宏任务队列为[timeout3]
  28. 同步代码执行输出:4,promise2 入队,微任务队列为:[promise2]
  29. timeout2 执行完毕,判断微任务队列是否为空,promise2 出队压入执行栈执行,微任务队列为空
  30. 同步代码执行输出:5
  31. promise2执行完,微任务队列为空,宏任务 timeout2 压入执行栈执行,宏任务队列为空
  32. 同步代码执行输出:11
  33. 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)
复制代码
  1. timeout1 200ms 后会放入到宏任务队列中
  2. while 执行了 200ms,此时 timeout1 已经先添加到宏任务队列中,因此最终打印结果为:1、2
  3. 如果将 while 的时间设置小于 200ms,考虑到代码执行需要花费时间,将 while 的条件改为Date.now() - s <= 198
  4. 测试 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相关推荐

  1. JavaScript中事件循环的理解 Event Loop

    为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop) 事件循环(Event Loop) 在JavaScript中,所有的任务都 ...

  2. 【js进阶】全面理解Event Loop这一篇就够了

    文章目录 一.前言 二.Event Loop知识铺垫 1.微任务(MircoTask) 2.宏任务(MacroTask/Task) 3.javascript runtime 三.浏览器环境的Event ...

  3. 跟着 Event loop 规范理解浏览器中的异步机制

    原文发自我的 GitHub blog,欢迎关注 前言 我们都知道 JavaScript 是一门单线程语言,这意味着同一事件只能执行一个任务,结束了才能去执行下一个.如果前面的任务没有执行完,后面的任务 ...

  4. 理解node.js中的 Event Loop

    node中的 "event loop" 正是node能处理高并发的核心所在.也正是因为它,node虽然在本质上是个单线程,却能让大量的操作处于后台运行.这篇文章将详细说明 even ...

  5. JavaScript 运行机制详解:Event Loop

    转自: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单 ...

  6. 【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

    PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:"看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲 ...

  7. JavaScript 运行机制详解:再谈Event Loop

    原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...

  8. Js Event Loop

    Js Event Loop 本文主要介绍 Node 中的事件循环 参考 官网 (什么是 Event Loop? - 阮一峰的网络日志 (ruanyifeng.com)) What is the Eve ...

  9. Event Loop事件循环机制

    转载自:阮一峰博客<JavaScript 运行机制详解:再谈Event Loop> 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个 ...

最新文章

  1. 下载MySQL数据库
  2. android 定义集合长度,Android Dex文件结构解析
  3. navcat设置oracle表主键自增_初识 Oracle 表空间设置与管理
  4. VC++ 模拟quot;CLICK事件quot;关闭指定窗体
  5. ICCV 2019 | 基于关联语义注意力模型的图像修复
  6. 【设计模式系列】行为型之策略模式
  7. 标识符怎么读_音标怎么学?到底该学英式还是美式
  8. java喷泉编码_Java干货分享使用JS实现简单喷泉效果
  9. Java8 函数式编程之函数接口(上)
  10. python3 验证用户名密码
  11. Python监控屏幕并截图保存
  12. Unable to process Jar entry
  13. Luogu5788 【模板】单调栈
  14. 解决Win10系统关机自动重启问题
  15. 阿里巴巴JAVA开发手册最新版pdf
  16. Air720x(3229)系列4G模块设计手册
  17. python实现游程编码(leetcode)
  18. 天河二号超级计算机能买到吗,天河二号计算机是巨型机吗
  19. Druid数据库连接池监控的使用
  20. Codeforces E. Game With String

热门文章

  1. New ADODB.Connection ADOX.Catalog 提示user-defined type not defined
  2. 9、ctemplate文档,简记(2)
  3. log4j的使用方法
  4. 状态保持中的cookie
  5. Struts的ONGL
  6. HDU2544 最短路(模版题dijkstra/floyd/spfa)
  7. apache关于记录真实客户端ip和不记录健康检查日志
  8. Javascript在IE中的有趣错误
  9. NEO智能合约反编译工具
  10. Spring编程式和声明式事务实例讲解