js事件循环
之前有看过一些事件循环的博客,不过一阵子没看就发现自己忘光了,所以决定来自己写一个博客总结下!
首先,我们来解释下事件循环是个什么东西:
就我们所知,浏览器的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)
那么,两者有什么具体的区别呢?或者说,如果两种任务同时出现的话,应该选择哪一个呢?
其实事件循环做的事情如下:
- 检查Macrotask 队列是否为空,若不为空,则进行下一步,若为空,则跳到3
- 从Macrotask队列中取队首(在队列时间最长)的任务进去执行栈中执行(仅仅一个),执行完后进入下一步
- 检查Microtask队列是否为空,若不为空,则进入下一步,否则,跳到1(开始新的事件循环)
- 从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
在上面的例子中
第一次事件循环:
- console.log(1)被执行,输出1
- settimeout1执行,加入macrotask队列
- setinterval1执行,加入macrotask队列
- settimeout2执行,加入macrotask队列
- promise2执行,它的两个then函数加入microtask队列
- console.log(9)执行,输出9
- 根据事件循环的定义,接下来会执行新增的microtask任务,按照进入队列的顺序,执行console.log(7)和console.log(8),输出7和8
microtask队列为空,回到第一步,进入下一个事件循环,此时macrotask队列为: settimeout1,setinterval1,settimeout2
第二次事件循环:
- 从macrotask队列里取位于队首的任务(settimeout1)并执行,输出2
microtask队列为空,回到第一步,进入下一个事件循环,此时macrotask队列为: setinterval1,settimeout2
第三次事件循环:
- 从macrotask队列里取位于队首的任务(setinterval1)并执行,输出3,然后又将新生成的setinterval1加入macrotask队列
microtask队列为空,回到第一步,进入下一个事件循环,此时macrotask队列为: settimeout2,setinterval1
第四次事件循环:
- 从macrotask队列里取位于队首的任务(settimeout2)并执行,输出10,并且执行new Promise内的函数(new Promise内的函数是同步操作,并不是异步操作),输出11,并且将它的两个then函数加入microtask队列
- 从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事件循环的更多相关文章
- Node.js 事件循环(Event Loop)介绍
Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...
- Node.js事件循环
Node JS是单线程应用程序,但它通过事件和回调概念,支持并发. 由于Node JS每一个API是异步的,作为一个单独的线程,它使用异步函数调用,以保持并发性.Node JS使用观察者模式.Node ...
- js事件循环机制辨析
对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...
- 6、Node.js 事件循环
#########################################################################################Node.js 事件循 ...
- js——事件循环
JS-事件循环 js运行的环境称之为宿主环境. 执行栈 :call stack ,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前他的相关信息会加入到执行栈中,函数调用之前,创建执行环境, ...
- Node.js 事件循环
Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...
- Node.js 学习(五)Node.js 事件循环
Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.j ...
- [译] 所有你需要知道的关于完全理解 Node.js 事件循环及其度量
原文地址:All you need to know to really understand the Node.js Event Loop and its Metrics 原文作者:Daniel Kh ...
- Node.js 事件循环机制
Node.js 采用事件驱动和异步 I/O 的方式,实现了一个单线程.高并发的 JavaScript 运行时环境,而单线程就意味着同一时间只能做一件事,那么 Node.js 如何通过单线程来实现高并发 ...
随机推荐
- junit测试Android项目
关于junit测试Android项目方法主要有一下步骤: 1.导入junit4的jar包 在工厂中Build Path中Add Library->JUnit->JUnit4->Fin ...
- WPF触屏Touch事件在嵌套控件中的响应问题
前几天遇到个touch事件的坑,记录下来以增强理解. 具体是 想把一个listview嵌套到另一个listview,这时候如果list view(子listview)的内容过多超过容器高度,它是不会出 ...
- 虚拟机下安装ubuntu系统
前期准备工具; 1,Oracle VM VirtualBox虚拟机 下载地址:http://pan.baidu.com/s/1miSaGvm 密码:c3dy 2,ubuntu系统文件 下载地址:htt ...
- Ruby中有意思的块
块:是在调用方法时,能与参数一起传递的多个处理的集合 简单点说,跟在方法执行后面的do |变量| end就是一个块,这个块会被传入方法中去执行! 这个非常厉害,非常有意思! 在ruby中,如果需要便利 ...
- leetcode contest 20
Q1: 520. Detect Capital Given a word, you need to judge whether the usage of capitals in it is right ...
- [原创]Jquery实现表格内容点击隐藏显示内容
1.首先看效果,点击红色字体,可是查看全部文字内容 2.JS实现 思路:将Ajax获得的数据,一份截取,一份不变,放到td里面,分别用span装着. 然后通过display属性,进行切换
- css之display:inline-block布局
css之display:inline-block布局 更好的排版-->查看原文 1.解释一下display的几个常用的属性值,inline , block, inline-block ●inli ...
- 北京赛车PK10 幸运飞艇 重庆时时彩 PC蛋蛋 快乐8 福彩3D 十分彩
QQ:1395239152 2017-3.14最新修复完整运营版时时彩源码PC+手机版本功能齐全 重庆时时彩是一种经中国国家财政部批准,重庆市福利彩票发行中心承销的福彩快开彩票,2元1注,分为&quo ...
- Redis入门学习
一.摘要 二.五种数据类型的基本命令操作 三.Redis连接池 四.普通同步方式 五.事务方式(Transactions) 六.管道(Pipelining) 七.管道中调用事务 八.分布式直连同步调用 ...
- vue之nextTick全面解析
vue的第一篇文章,介绍一下简单的nextTick方法的实现原理 简介 vue是非常流行的框架,他结合了angular和react的优点,从而形成了一个轻量级的易上手的具有双向数据绑定特性的mvvm框 ...