JS中的事件循环机制与宏队列、微队列笔记
01-基本概念
1.1-JavaScript是单线程语言
为什么JavaScript是一门单线程语言?作为一门浏览器脚本语言,它的主要用途就是操作DOM和与用户交互设计,如果说js是多线程的话,那么它在操作DOM的时候,一个线程对DOM进行了新增操作,另一个线程对DOM进行了删除操作,那么这个时候js的处理将会变得十分复杂。为了避免这种情况,JavaScript一诞生就是单线程。
1.2-任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。所有任务可以分成两种,一种是同步任务,另一种是异步任务。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;所有同步任务都在主线程上执行,形成一个执行栈;
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
也就是说:当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。
1.3-事件循环
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
主线程在运行的时候,产生堆和栈,栈中的代码调用外部的API,它们会在“任务队列”中加入各种事件。只要栈中的代码执行完毕,主线程就会去读取“任务队列”中的回调函数依次执行。
1.4-宏队列和微队列
在任务队列中,其实又分为宏队列和微队列,他们的执行优先级也有区别,那么哪些回调函数放在宏队列,哪些回调函数放在微队列呢?
宏队列:dom事件回调、ajax回调、定时器回调
微队列:promise回调、mutation回调
因此JS执行时首先必须执行所有的初始化同步任务代码,执行完以后,每次准备取出第一个宏任务执行之前,都要将所有的微任务一个一个取出来执行。
让我们看一个案例帮助我们理解,例1:
setTimeout(() => {
console.log('timeout callback()1');
Promise.resolve(3).then(
value => {
console.log('Promise onResolved3()', value);
}
)
}, 0)
setTimeout(() => {
console.log('timeout callback2()');
}, 0)
Promise.resolve(1).then(
value => {
console.log('Promise onResolved1()', value);
}
)
Promise.resolve(2).then(
value => {
console.log('Promise onResolved2()', value);
}
)
上面的代码中写了两个setTimeout定时器函数,在里面写入了打印输出的回调,以及两个状态为resolved的Promise对象,在then方法中写入了两个打印输出的回调,我们已经知道了宏队列和微队列的执行流程,那么我们来分析一下上面代码的执行流程。
- 1.首先看上面的代码中有没有同步代码任务,发现没有可以直接对任务队列中的异步回调进行分析。
- 2.setTimeout定时器的回调函数将会放入宏队列中,而Promise中的回调将会放入微队列中。
- 3.我们知道执行第一个宏任务之前都要把微队列的任务全部取出执行完毕才能执行宏任务,因此可以分析出,上面代码将会优先打印出
'Promise onResolved1()', 1
与'Promise onResolved2()', 2
- 4.当微队列中的任务执行完开始取出宏队列中的第一个宏任务执行,也就是第一个setTimeout中的回调函数,因此将会打印
'timeout callback()1'
- 5.因为执行完这个打印输出之后,下面又有一个立即resolved的Promise对象,所以我们需要把这个Promise中的回调再次加入微队列中。
- 6.现在宏队列还有一个定时器回调,微队列中又多了一个微任务,因此我们需要先执行微队列中的回调,所以将会打印输出
'Promise onResolved3()', 3
- 7.微队列中的回调执行完毕后,再执行宏队列中的任务,因此最后打印
timeout callback2()
所以最后的打印顺序应该是:
Promise onResolved1() 1
Promise onResolved2() 2
timeout callback()1
Promise onResolved3() 3
timeout callback2()
看完上面这道简单的例子,我们再来看一下第二道比较复杂的题,看看能否判断出正确的输出顺序,例二:
setTimeout(() => {
console.log('0');
}, 0)
new Promise((resolve, reject) => {
console.log('1');
resolve();
}).then(() => {
console.log('2');
new Promise((resolve, reject) => {
console.log('3');
resolve()
}).then(() => {
console.log('4');
}).then(() => {
console.log('5');
})
}).then(() => {
console.log('6');
})
new Promise((resolve, reject) => {
console.log('7');
resolve();
}).then(() => {
console.log('8');
})
分析:
1、从上往下看没有同步代码,首先将定时器中的回调添加到宏任务队列中,所以现在宏任务队列中的任务[0]
2、再执行Promise代码,将先同步代码所以打印1
,然后状态立马变成resolve,所以将其中的异步回调函数打印代码加入到微队列中[2]
3、微队列中2还没有打印取出,所以我们再看下一个Promise中的代码,先将打印同步代码7
,然后立即变为resolve状态,并将异步回调打印8的代码放入微队列中所以现在的微队列是:[2,8]
4、所有的执行完之后我们要先把微队列中的代码都取出执行完再去执行后面的代码以及宏队列的代码,所以先取出2,即打印2
,所以现在的微队列只有一个任务[8]
5、所以现在已经打印了1,7,2
,打印完2以后我们再从这一行代码往下看又new了一个Promise对象,里面有同步代码打印3
,然后立即变为resolve状态,因此将4放入微队列[8,4]
6、接下来这一步要非常注意:在我们没有打印4的时候,那么我们是不会把后面then方法中的5放入微队列中的,我们会先将外层Promise中的then中的6放入微队列,因为内层的Promise已经执行完最后一个then方法了,因此现在的微队列是[8,4,6]
7、现在开始取出微队列中的任务进行执行,将依次打印8
,打印完8后面没有其余代码,因此急需取出打印4的任务再打印4
,当打印完4,之后那么就会将后面then中的打印5的异步任务放入微队列,因此现在的微队列是[6,5]
,目前的打印顺序是1,7,2,3,8,4
。
8、最后我们只需要将微队列中剩余的任务和宏队列中剩余的任务取出执行便分析完了整个顺序流程,因此最后的打印顺序应该是1,7,2,3,8,4,6,5,0
- Java集合深度解析之LinkedList
- 2015编程之美(资格赛)--基站选址
- CentOS7下Nginx服务器安装与使用教程
- 2015年编程之美(资格赛) ---2月29日
- 编程之美--2. Trie树 (Trie图)
- 编程之美 --1 : 骨牌覆盖问题·一
- go语言中的数组切片:特立独行的可变数组
- go语言单例模式(Singleton)实例分析
- C++ template的一些高级用法(元编码,可变参数,仿函数,using使用方法,. C++ 智能指针)
- Golang memory model
- go的非侵入式接口
- C++ 如何重复利用一个内存地址块
- 如何减轻挖矿攻击给企业安全带来的威胁
- C++ 高级语法学习与总结(代码实例)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法