先来一道常见的面试题:
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是单线程的”指的是JS 引擎线程。
概念2:宿主
JS运行的环境。一般为浏览器或者Node。
概念3:执行栈
是一个存储函数调用的栈结构,遵循先进后出的原则。
function foo() {
throw new Error('error')
}
function bar() {
foo()
}
bar()
stack.jpg
当开始执行 JS 代码时,首先会执行一个 main
函数,然后执行我们的代码。根据先进后出的原则,后执行的函数会先弹出栈,在图中我们也可以发现,foo
函数后执行,当执行完毕后就从栈中弹出了。
概念4:Event Loop
JS到底是怎么运行的呢?
image
Event Loop中,每一次循环称为tick,每一次tick的任务如下:
- 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
- 检查是否存在微任务,有则会执行至微任务队列为空;
- 如果宿主为浏览器,可能会渲染页面;
- 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。
概念5:宏任务和微任务
在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise
,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。
所以,总结一下,两者区别为:
宏任务(macrotask) | 微任务(microtask) | 谁发起的 | 宿主(Node、浏览器) | JS引擎 | 具体事件 | 1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js) | 1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js) | 谁先运行 | 后运行 | 先运行 | 会触发新一轮Tick吗 | 会 | 不会 |
---|
拓展 1: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
中的回调。
拓展 2:setTimeout
,setImmediate
谁先执行?
setImmediate
和process.nextTick
为Node环境下常用的方法(IE11支持setImmediate
),所以,后续的分析都基于Node宿主。
Node.js是运行在服务端的js,虽然用到也是V8引擎,但由于服务目的和环境不同,导致了它的API与原生JS有些区别,其Event Loop还要处理一些I/O,比如新的网络连接等,所以与浏览器Event Loop不太一样。
一般来说,setImmediate
会在setTimeout
之前执行,如下:
console.log('outer');
setTimeout(() => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
}, 0);
其执行顺序为:
- 外层是一个setTimeout,所以执行它的回调的时候已经在timers阶段了
- 处理里面的setTimeout,因为本次循环的timers正在执行,所以其回调其实加到了下个timers阶段
- 处理里面的setImmediate,将它的回调加入check阶段的队列
- 外层timers阶段执行完,进入pending callbacks,idle, prepare,poll,这几个队列都是空的,所以继续往下
- 到了check阶段,发现了setImmediate的回调,拿出来执行
- 然后是close callbacks,队列是空的,跳过
- 又是timers阶段,执行
console.log('setTimeout')
但是,如果当前执行环境不是timers阶段,就不一定了。。。。顺便科普一下Node里面对setTimeout
的特殊处理:setTimeout(fn, 0)
会被强制改为setTimeout(fn, 1)
。
看看下面的例子:
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
其执行顺序为:
- 遇到
setTimeout
,虽然设置的是0毫秒触发,但是被node.js强制改为1毫秒,塞入times阶段 - 遇到
setImmediate
塞入check阶段 - 同步代码执行完毕,进入
Event Loop
- 先进入
times
阶段,检查当前时间过去了1毫秒没有,如果过了1毫秒,满足setTimeout
条件,执行回调,如果没过1毫秒,跳过 - 跳过空的阶段,进入check阶段,执行
setImmediate
回调
可见,1毫秒是个关键点,所以在上面的例子中,setImmediate
不一定在setTimeout
之前执行了。
拓展 3:Promise
,process.nextTick
谁先执行?
因为process.nextTick
为Node环境下的方法,所以后续的分析依旧基于Node。
process.nextTick()
是一个特殊的异步API,其不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。
所以,nextTick
和Promise
同时出现时,肯定是nextTick
先执行,原因是nextTick
的队列比Promise
队列优先级更高。
拓展 4:应用场景 - Vue中的vm.$nextTick
vm.$nextTick
接受一个回调函数作为参数,用于将回调延迟到下次DOM更新周期之后执行。
这个API就是基于事件循环实现的。
“下次DOM更新周期”的意思就是下次微任务执行时更新DOM,而vm.$nextTick
就是将回调函数添加到微任务中(在特殊情况下会降级为宏任务)。
因为微任务优先级太高,Vue 2.4版本之后,提供了强制使用宏任务的方法。
小结
下面是道加强版的考题,大家可以试一试。
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')
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!