目录
- 1、关于JavaScript
- 2、JavaScript事件循环
- 3、宏任务和微任务
- 4、拓展宏任务微任务
下面一道关于宏任务和微任务的题:
setTimeout(function(){ console.log('1') }); new Promise(function(resolve){ console.log('2'); resolve(); }).then(function(){ console.log('3') }); console.log('4')
试问一下上面代码的执行顺序是啥?
有小伙伴可能会答:2,4,1,3
估摸着是这么想的:我难道不知道js是一行一行执行的,setTimeout
是异步,所以先放后面;往下走,执行了console.log(2)
,.then()是异步的,放在后面;走了console.log(4);
再去异步队列里走,先是console.log(1);
再是console.log(3)
。
我有点慌,于是我粘贴到浏览器去瞅两眼:
无法相信
带着困惑的我,只能去好好研究研究JavaScript的运行机制了!
1、关于JavaScript
JavaScript
是一门单线程语言,即一次只能完成一个任务,若有多个任务要执行,则必须排队按照队列来执行(前一个任务完成,再执行下一个任务)。
2、JavaScript事件循环
既然js是单线程,那就像只有一个窗口的食堂,学生需要排队一个一个打饭,同理js任务也要一个一个顺序执行。这种模式执行简单,但随着日后的需求,事务,请求增多,这种单线程模式执行效率必定低下。只要有一个任务执行消耗了很长时间,在这个时间里后面的任务无法执行。
常见的有新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?为了解决这个问题,JavaScript语言将任务执行模式分成同步和异步:
- 同步模式: 就是上面所说的一种执行模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
- 异步模式: 就是每一个任务有一个或多个回调函数(
callback
),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
导图要表达的内容用文字来表述的话:
- 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入
Event Table
并注册函数。 - 当指定的事情完成时,
Event Table
会将这个函数移入Event Queue
。 - 主线程内的任务执行完毕为空,会去
Event Queue
读取对应的函数,进入主线程执行。 - 上述过程会不断重复,也就是常说的
Event Loop
(事件循环)。
再配上代码表达:
let data = []; $.ajax({ url:blog.csdn.net, data:data, success:() => { console.log('发送成功!'); } }) console.log('代码执行结束');
上面是一段简易的ajax请求代码:
ajax
进入Event Table
,注册回调函数success
。- 执行
console.log
(‘代码执行结束’)。 - ajax事件完成,回调函数
success
进入Event Queue
。 - 主线程从
Event Queue
读取回调函数success
并执行。
相信通过上面的文字和代码,你已经对js的执行顺序有了初步了解。然而这也是为什么会有小伙伴回答2,4,1,3的原因。
然而实际上,异步队列里是还有门道的,我们那道面试题,setTimeout
和promise
的.then()
都在异步队列了!接下来,讲讲那些门道(宏任务和微任务)。
3、宏任务和微任务
每个人的理解方式不同,因为宏任务和微任务并不是标准,但执行的顺序在js中是大一统了的。
宏任务:
# | 浏览器 | Node |
<script>整体代码 | √ | √ |
setTimeout | √ | √ |
setInterval | √ | √ |
setImmediate | x | √ |
requestAnimationFrame | √ | x |
Ajax | √ | √ |
DOM事件 | √ | √ |
微任务:
# | 浏览器 | Node |
process.nextTick | x | √ |
MutationObserver | √ | x |
Promise.then catch finally | √ | √ |
宏任务包括:<script>整体代码、setTimeout
、setInterval
、setImmediate
、Ajax
、DOM事件
微任务:process.nextTick
、MutationObserver
、Promise.then catch finally
process.nextTick差异太大,不同的node执行不统一,不做标准
微任务比宏任务的执行时间要早
Tip:有些人喜欢将<script>整体代码放在宏任务里,但我个人不喜欢,在我这里它只是第一执行的主线程,我个人是将宏任务和微任务都归类到异步任务里!
我们再来看看那道面试题:
//回调才是异步任务 setTimeout(function(){//宏任务 console.log('1') }); new Promise(function(resolve){ console.log('2');//同步主线程 resolve(); }).then(function(){//微任务 console.log('3') }); console.log('4')//同步主线程
2:同步中的第一个,故第一
4:同步中的第二个,故第二
3:异步中的微任务,故第三
1:异步中的宏任务,故第二
因此:2,4,3,1的结果就出来了!
除此我们来拓展一下:
setTimeout(() => {//宏任务队列1 console.log('1');//宏任务队1的任务1 setTimeout(() => {//宏任务队列3(宏任务队列1中的宏任务) console.log('2') }, 0) new Promise(resolve => { resolve() console.log('3')//宏任务队列1的任务2 }).then(() => {//宏任务队列1中的微任务 console.log('4') }) }, 0) setTimeout(() => {//宏任务队列2 console.log('5') }, 0) console.log('6')//同步主线程
执行整体代码(宏任务)console.log(‘6’) >> 宏任务队列1、宏任务队列2位异步(依次执行)
宏任务队列1:=>
console.log(‘1’)
console.log(‘3’)
console.log(‘4’)//宏任务中的微任务
剩下的不会先执行,因为是宏任务中的宏任务(console.log(2)) ,要被继续丢进任务队列后
宏任务队列2:=>
console.log(‘5’)
宏任务队列1中的宏任务3:=>
console.log(‘2’)
以上代码会怎么输出呢?
4、拓展宏任务微任务
上面出了复杂的题,小伙伴们不妨可以想一想,这种复杂情况,一个套一个的该怎么执行呢?
- 6:第一个同步主线程,故第一
script整体代码里没有微任务故直接执行宏任务=>
宏任务队列:
宏任务队列1
- 任务1:console.log(1)
- 任务2:console.log(3)
- 宏任务队列1中的微任务:console.log(4)
- 宏任务队列3:因他是宏任务队列1中的宏任务,所以被丢进了任务队列最后,我们先看宏任务队列1同级的是否还有宏任务,有就先执行同级的,没有才可以执行宏任务队列3!故最后!
宏任务队列2
console.log(5)
所以输出的结果是什么?是6,1,3,4,5,2!
经过验证,结果正确!
到此这篇关于浅谈js中的宏任务和微任务的文章就介绍到这了,更多相关js中的宏任务和微任务内容请搜索NICE源码以前的文章或继续浏览下面的相关文章希望大家以后多多支持NICE源码!