之前有看过一些事件循环的博客,不过一阵子没看就发现自己忘光了,所以决定来自己写一个博客总结下!

首先,我们来解释下事件循环是个什么东西:

就我们所知,浏览器的js是单线程的,也就是说,在同一时刻,最多也只有一个代码段在执行,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?我们先来看一张图(这张图来自于http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html)



从上图我们可以看出,js主线程它是有一个执行栈的,所有的js代码都会在执行栈里运行。在执行代码过程中,如果遇到一些异步代码(比如setTimeout,ajax,promise.then以及用户点击等操作),那么浏览器就会将这些代码放到一个线程(在这里我们叫做幕后线程)中去等待,不阻塞主线程的执行,主线程继续执行栈中剩余的代码,当幕后线程(background thread)里的代码准备好了(比如setTimeout时间到了,ajax请求得到响应),该线程就会将它的回调函数放到任务队列中等待执行。而当主线程执行完栈中的所有代码后,它就会检查任务队列是否有任务要执行,如果有任务要执行的话,那么就将该任务放到执行栈中执行。如果当前任务队列为空的话,它就会一直循环等待任务到来。因此,这叫做事件循环。

那么,问题来了。如果任务队列中,有很多个任务的话,那么要先执行哪一个任务呢?

其实(正如上图所示),js是有两个任务队列的,一个叫做Macrotask Queue(Task Queue),一个叫做Microtask Queue

  • 前者主要是进行一些比较大型的工作,常见的有setTimeout,setInterval,用户交互操作,UI渲染等
  • 后者主要是进行一些比较小型的工作,常见的有Promise,process.nextTick(nodejs)

那么,两者有什么具体的区别呢?或者说,如果两种任务同时出现的话,应该选择哪一个呢?

其实事件循环做的事情如下:

  1. 检查Macrotask 队列是否为空,若不为空,则进行下一步,若为空,则跳到3
  2. 从Macrotask队列中取队首(在队列时间最长)的任务进去执行栈中执行(仅仅一个),执行完后进入下一步
  3. 检查Microtask队列是否为空,若不为空,则进入下一步,否则,跳到1(开始新的事件循环)
  4. 从Microtask队列中取队首(在队列时间最长)的任务进去事件队列执行,执行完后,跳到3

其中,在执行代码过程中新增的microtask任务会在当前事件循环周期内执行,而新增的macrotask任务只能等到下一个事件循环才能执行了(一个事件循环只执行一个macrotask)

首先,我们先来看一段代码

console.log(1)
setTimeout(function() {
//settimeout1
console.log(2)
}, 0);
const intervalId = setInterval(function() {
//setinterval1
console.log(3)
}, 0)
setTimeout(function() {
//settimeout2
console.log(10)
new Promise(function(resolve) {
//promise1
console.log(11)
resolve()
})
.then(function() {
console.log(12)
})
.then(function() {
console.log(13)
clearInterval(intervalId)
})
}, 0); //promise2
Promise.resolve()
.then(function() {
console.log(7)
})
.then(function() {
console.log(8)
})
console.log(9)

你觉得结果应该是什么呢?

我在node环境和chrome控制台输出的结果如下:

1
9
7
8
2
3
10
11
12
13

在上面的例子中

第一次事件循环:

  1. console.log(1)被执行,输出1
  2. settimeout1执行,加入macrotask队列
  3. setinterval1执行,加入macrotask队列
  4. settimeout2执行,加入macrotask队列
  5. promise2执行,它的两个then函数加入microtask队列
  6. console.log(9)执行,输出9
  7. 根据事件循环的定义,接下来会执行新增的microtask任务,按照进入队列的顺序,执行console.log(7)和console.log(8),输出7和8

    microtask队列为空,回到第一步,进入下一个事件循环,此时macrotask队列为: settimeout1,setinterval1,settimeout2

第二次事件循环:

  1. 从macrotask队列里取位于队首的任务(settimeout1)并执行,输出2

    microtask队列为空,回到第一步,进入下一个事件循环,此时macrotask队列为: setinterval1,settimeout2

第三次事件循环:

  1. 从macrotask队列里取位于队首的任务(setinterval1)并执行,输出3,然后又将新生成的setinterval1加入macrotask队列

    microtask队列为空,回到第一步,进入下一个事件循环,此时macrotask队列为: settimeout2,setinterval1

第四次事件循环:

  1. 从macrotask队列里取位于队首的任务(settimeout2)并执行,输出10,并且执行new Promise内的函数(new Promise内的函数是同步操作,并不是异步操作),输出11,并且将它的两个then函数加入microtask队列
  2. 从microtask队列中,取队首的任务执行,直到为空为止。因此,两个新增的microtask任务按顺序执行,输出12和13,并且将setinterval1清空

    此时,microtask队列和macrotask队列都为空,浏览器会一直检查队列是否为空,等待新的任务加入队列。

在这里,大家可以会想,在第一次循环中,为什么不是macrotask先执行?因为按照流程的话,不应该是先检查macrotask队列是否为空,再检查microtask队列吗?

原因:因为一开始js主线程中跑的任务就是macrotask任务,而根据事件循环的流程,一次事件循环只会执行一个macrotask任务,因此,执行完主线程的代码后,它就去从microtask队列里取队首任务来执行。

注意:

由于在执行microtask任务的时候,只有当microtask队列为空的时候,它才会进入下一个事件循环,因此,如果它源源不断地产生新的microtask任务,就会导致主线程一直在执行microtask任务,而没有办法执行macrotask任务,这样我们就无法进行UI渲染/IO操作/ajax请求了,因此,我们应该避免这种情况发生。在nodejs里的process.nexttick里,就可以设置最大的调用次数,以此来防止阻塞主线程。

以此,我们来引入一个新的问题,定时器的问题。定时器是否是真实可靠的呢?比如我执行一个命令:setTimeout(task, 100),他是否就能准确的在100毫秒后执行呢?其实根据以上的讨论,我们就可以得知,这是不可能的。

原因我想大家应该也都知道了,因为你执行setTimeout(task,100)后,其实只是确保这个任务,会在100毫秒后进入macrotask队列,但并不意味着他能立刻运行,可能当前主线程正在进行一个耗时的操作,也可能目前microtask队列有很多个任务,所以这也可能是大家一直诟病setTimeout的原因吧哈哈哈哈

以上,只是我个人对事件循环的一些看法, 以及借鉴了其他优秀文章

参考:

http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html

https://github.com/ccforward/cc/issues/47

js事件循环的更多相关文章

  1. Node.js 事件循环(Event Loop)介绍

    Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...

  2. Node.js事件循环

    Node JS是单线程应用程序,但它通过事件和回调概念,支持并发. 由于Node JS每一个API是异步的,作为一个单独的线程,它使用异步函数调用,以保持并发性.Node JS使用观察者模式.Node ...

  3. js事件循环机制辨析

     对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...

  4. 6、Node.js 事件循环

    #########################################################################################Node.js 事件循 ...

  5. js——事件循环

    JS-事件循环 js运行的环境称之为宿主环境. 执行栈 :call stack ,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前他的相关信息会加入到执行栈中,函数调用之前,创建执行环境, ...

  6. Node.js 事件循环

    Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...

  7. Node.js 学习(五)Node.js 事件循环

    Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...

  8. [译] 所有你需要知道的关于完全理解 Node.js 事件循环及其度量

    原文地址:All you need to know to really understand the Node.js Event Loop and its Metrics 原文作者:Daniel Kh ...

  9. Node.js 事件循环机制

    Node.js 采用事件驱动和异步 I/O 的方式,实现了一个单线程.高并发的 JavaScript 运行时环境,而单线程就意味着同一时间只能做一件事,那么 Node.js 如何通过单线程来实现高并发 ...

随机推荐

  1. Json对象和Json字符串之间的转换

    json字符串转json对象,调用parse方法: var b='{"name":"2323","sex":"afasdf&quo ...

  2. ES6入门

    整理了ES6常用的一些语法,跟大家分享(promise.generator什么的还没有梳理清楚,后续再更新...) 1⃣️ 变量声明-let 与 const (首先关于测试结果:这里有个小问题,如果用 ...

  3. 为linux安装xen-tools提示/dev/xvdd does not exist

    看样子百度还是不如google啊.百度上找到的信息完全无用.google上却给我找到了... 1:当/dev/xvdd does not exist错误出现时,可以尝试下 mount /dev/cdr ...

  4. linux网络编程(socket)之面向连接(TCP/IP)

    1.流程 服务器: 创建socket: 绑定端口: 监听: 监听到有连接请求,接受请求: 建立连接,开始对话. 客户端: 创建socket: 请求建立连接: 连接建立成功,开始对话. 2.实例代码 / ...

  5. SQL Server pivot 行转列遇到的问题

    前段时间开发系统时,有个功能是动态加载列,于是就使用了SQL Server自带的PIVOT函数进行行转列,开始用的非常溜,效果非常好.但是提交测试后问题来了,测试添加的列名中包含空格,然后就杯具了,功 ...

  6. Kafka协议兼容性改进

    在Kafka 0.10.2.0之前,Kafka服务器端和客户端版本之间的兼容性是"单向"的,即高版本的broker可以处理低版本client的请求.反过来,低版本的broker不能 ...

  7. bzoj3939 【USACO 2015 FEB GOLD 】cow hopscotch

    Description 就像人类喜欢玩"跳房子"的游戏,农民约翰的奶牛已经发明了该游戏的一个变种自己玩.由于笨拙的动物体重近一吨打,牛跳房子几乎总是以灾难告终,但这是没有阻止奶牛几 ...

  8. C#1所搭建的核心基础

    一,委托 委托封装了包含特殊返回类型和一组参数的行为,类似包含单一方法接口. 委托类型声明中所描述的类型签名决定了哪个方法可以用于创建委托实例,同时决定了调用的签名:委托类型实际上只是参数类型的一个列 ...

  9. 【山东省选2008】郁闷的小J 平衡树Treap

    小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架.虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危险,这也正是他所郁闷的.具体说来,书架由N ...

  10. css写出三角形(兼容IE)

    css写出三角形   利用css写三角形,兼容IE7 .arrow-up { width:0px; height:0px; border-left:10px solid transparent; bo ...