事件循环与任务队列
来源:互联网 发布:基因毒性杂质数据库 编辑:程序博客网 时间:2024/06/05 10:39
本文首发于我的博客,这是我的github,欢迎star。
在网上找了很多事件循环和任务队列相关的文章,有些说的不是很清楚,看完感觉还是有些晕晕乎乎,所以写这篇博客把整体思路梳理一下。如果你有什么不同的理解,或是疑惑的地方,可以留言讨论。这里是我的github,欢迎来访。
事件循环与任务队列是
JS
中比较重要的两个概念。这两个概念在ES5
和ES6
两个标准中有不同的实现。尤其在ES6
标准中,清楚的区分宏观任务队列和微观任务队列才能解释Promise
一些看似奇怪的表现。
事件循环
事件循环是什么?为什么要有事件循环这个东西?我们都知道JS
是单线程的,但是像Ajax
,或是DOM
事件这种很耗时的操作,需要用并发处理,否则单线程会长时间等待,什么也做不了。而单线程循环就是并发的一种形式,一个线程中只有一个事件循环。而任务队列是用来配合事件循环完成操作的,一个线程可以拥有多个任务队列。
任务队列
任务队列是什么?故名思意,排着任务的队列。所谓任务是WebAPIs
返回的一个个通知,让JS
主线程在读取任务队列的时候得知这个异步任务已经完成,下一步该执行这个任务的回调函数了。主线程拥有多个任务队列,不同的任务队列用来排列来自不同任务源的任务。任务源是什么?像setTimeout/Promise/DOM事件等都是任务源,来自同类任务源的任务我们称它们是同源的,比如setTimeout与setInterval就是同源的。在ES6
标准中任务队列又分为宏观任务队列和微观任务队列,我们后边再详细讨论。
下面先通俗的讲述一下ES5中事件循环到底是怎么循环的,如图(据阮一峰前辈的教程):
图中有三大块:
- 函数调用栈:即执行栈。
- WebAPIs:浏览器的接口。比如一个Ajax
操作,主线程会把收发Ajax
交给浏览器的API
,之后就继续做别的事情,浏览器在接收到Ajax
返回的数据之后,会把一个Ajax
完成的事件排到相应的任务队列后边。
- 任务队列们:主线程中有多个任务队列,同源的任务排在属于自己的任务队列。
一个具体点的栗子。比如现在打开了一个页面,里边有一段<script>
,其中有Ajax
,DOM
操作等等。这段JS
是在浏览器提供的全局环境(浏览器中是window
)里执行的,执行中遇到函数调用时会压入执行栈。
- 主线程在遇到
Ajax
或是setTimeout
这种异步操作时会交给浏览器的WebAPIs
,然后继续执行后边的代码,直到最后执行栈为空。 - 浏览器会在不确定的时间将完成的任务返回,排到相应的任务队列后。
- 执行栈为空后,主线程会到任务队列中去取任务,这些任务会告诉下一步应该执行哪些回调函数。任务队列是具有优先级的,按照优先级决定访问的先后顺序。而优先级在不同的环境中会有所不同,所以不能给出一个固定的优先级。
- 每访问一个队列,执行栈会执行完这个任务队列的所有的代码,然后再取下一个任务队列需要执行的的代码。如果在执行中遇到了当前属于任务队列的异步任务时。此次任务的返回不会直接排到当前任务队列之后。因为这属于两次不同的事件循环,会被区分开来。
就这样循环执行,直到三大块全为空,这称为事件循环。
微观任务队列
ES6
标准中任务队列存在两种类型,一种就是上边提到的一些队列,比如setTimeout
、网络请求Ajax
、用户I\O
等都属于宏观任务队列(macrotask queue),另一种是微观任务队列(microtask queue),Promise
就属于微观任务队列。
添加了微观任务队列之后事件循环有什么变化呢?在执行栈执行的过程中会把属于微观任务队列的任务分配到相应的微观任务队列中去。而在调用栈执行空之后,主线程读取任务队列时,会先读取所有微观任务队列,然后读取一个宏观任务队列,再读取所有的微观任务队列。如图:
好了,说了很多概念上的东西,不如一段代码来的清晰:
setTimeout(function(){console.log(4)},0);new Promise(function(resolve){ console.log(1) for( var i=0 ; i<10000 ; i++ ){ i==9999 && resolve() } console.log(2)}).then(function(){ console.log(5)});console.log(3);
- 脚本开始执行,最先遇到
setTimeout
,交给浏览器去计时,达到setTimeout
限制最短计时之后,把这个任务推入setTimeout
队列。 - 遇到
Promise
构造函数,构造函数参数执行,输出1
,调用resolve
改变Promise
对象的状态,然后输出2
。 Promise
对象调用then
方法,将这个任务推入Promise
任务队列。- 执行
console.log(3)
,输出3
。 - 调用栈为空,读取任务队列,按照
读取所有微观任务队列 -> 执行 ->
读取一个宏观任务队列 -> 执行 ->
读取所有微观任务队列 -> 执行 ->
再读取一个宏观任务队列…的顺序。 - 读取所有微观任务队列中的任务,执行这些任务指定的回调函数。执行
then
指定的回调函数,输出5
(微观任务队列也具有优先级)。 最后读取到
setTimeout
的任务,执行回调函数,输出4
。所以最后的输出顺序是
1,2,3,5,4
,而不是1,2,3,4,5
。如果不清楚微观任务队列的执行机制,很容易将两个异步任务归为一类,将执行顺序判断错误。
到这里算是把事件循环和任务队列说的比较清楚了,参考了很多大佬的博客与讨论:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
https://www.zhihu.com/question/36972010
http://www.jianshu.com/p/12b9f73c5a4f
http://www.cnblogs.com/hity-tt/p/6733062.html
如果你有不同的理解请到博客下方留言,这是我的github,欢迎来访,你的star
就是我的动力。
- 事件循环与任务队列
- 事件循环和任务队列
- javascript线程,任务队列和事件循环
- JS:事件循环机制、调用栈以及任务队列
- 队列与循环队列
- 事件循环机制-任务队列、webAPI、JS主线程的相互协同
- 优先队列与循环队列
- 顺序队列与循环队列
- 顺序队列与循环队列
- GCD队列与任务
- 队列之顺序队列与循环队列
- 队列---循环队列与链队列比较
- 队列之顺序队列与循环队列
- JavaScript队列、优先队列与循环队列
- 定时任务at与循环任务crontab
- C++任务队列与多线程
- C++任务队列与多线程
- C++任务队列与多线程
- Linux下PHP安装redis扩展
- C语言中用循环打印各种图形
- Linux系统基础(十二)
- LeetCode题解系列--718. Maximum Length of Repeated Subarray
- 删数问题(山东理工2072)
- 事件循环与任务队列
- xamarin学习笔记A16(安卓OkHttp3和HttpURLConnection)下
- java习题
- MySQL中给中文进行排序
- final修饰类和方法
- 滚动条
- 代理模式
- 表单
- Java消息中间件学习笔记五 -- ActiveMQ的使用【主题模式】