先来一道常见的面试题:
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('promise')
resolve()
})
.then(() => {
console.log('then1')
})
.then(() => {
console.log('then2')
})
console.log('end')
应该不少同学都能答出来,结果为:
start
promise
end
then1
then2
setTimeout
这个就涉及到JavaScript事件轮询中的宏任务和微任务。那么,你能说清楚到底宏任务和微任务是什么?是谁发起的?为什么微任务的执行要先于宏任务呢?
首先,我们需要先知道JS运行机制。
JS运行机制
概念1: JS是单线程执行
浏览器有JS 引擎线程和渲染线程,且两个线程互斥。
概念2:执行栈
是一个存储函数调用的栈结构,遵循先进后出的原则。
function foo() {
throw new Error('error')
}
function bar() {
foo()
}
bar()
stack.jpg
当开始执行 JS 代码时,首先会执行一个 main
函数,然后执行我们的代码。根据先进后出的原则,后执行的函数会先弹出栈,在图中我们也可以发现,foo
函数后执行,当执行完毕后就从栈中弹出了。
概念3:宿主
JS运行的环境。一般为浏览器或者Node。
概念4:Event Loop
JS到底是怎么运行的呢?
imageJS引擎常驻于内存中,等待宿主将JS代码或函数传递给它。
也就是等待宿主环境分配宏观任务,反复等待 - 执行即为事件循环。
Event Loop中,每一次循环称为tick,每一次tick的任务如下:
- 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
- 检查是否存在微任务,有则会执行至微任务队列为空;
- 如有必要会渲染页面;
- 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。
概念5:宏任务和微任务
ES6 规范中,microtask 称为
jobs
,macrotask 称为task
宏任务是由宿主发起的,而微任务由JavaScript自身发起。
在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise
,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。
所以,总结一下,两者区别为:
宏任务(macrotask) | 微任务(microtask) | |
---|---|---|
谁发起的 | 宿主(Node、浏览器) | JS引擎 |
具体事件 | script、setTimeout、setInterval、I/O、UI rendering、postMessage、MessageChannel、setImmediate | Promise、MutaionObserver、process.nextTick |
谁先运行 | 后运行 | 先运行 |
会触发新一轮Tick吗 | 会 | 不会 |
拓展:async
和await
是如何处理异步任务的?
简单说,async
是通过Promise
包装异步任务。
比如有如下代码:
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
改为ES5的写法:
new Promise((resolve, reject) => {
// console.log('async2 end')
async2()
...
}).then(() => {
// 执行async1()函数await之后的语句
console.log('async1 end')
})
当调用 async1
函数时,会马上输出 async2 end
,并且函数返回一个 Promise
,接下来在遇到 await
的时候会就让出线程开始执行 async1
外的代码(可以把 await
看成是让出线程的标志)。
然后当同步代码全部执行完毕以后,就会去执行所有的异步代码,那么又会回到 await
的位置,去执行 then
中的回调。
小结
下面是道加强版的考题,大家可以试一试。
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
网友评论