首先,看一道面试题
//请写出输出内容
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
这道题目需要我们了解js的运行机制
单线程
js的一大特点就是单线程,也就是说同一时间只能做一件事情。
js单线程的原因就是它是一个脚本语言,主要用于用户交互以及操作DOM。如果是多线程,那么如果一个线程删除了DOM,另一个线程又在操作这个DOM,那么应该以哪个为准呢?所以他的用途决定了它只能是单线程
任务队列
单线程就表示所有的任务都要排队,只有前面的任务执行完毕之后才能执行下一个任务。
有的时候因为ajax请求等任务需要等待的时间很长,如果要等到请求完成在执行下一个任务,会浪费大量时间。此时js的设计者就想到了,可以将这些任务挂起,继续执行下面的任务,等到请求有了结果再去执行挂起的任务。
于是所有的任务可以分成同步任务和异步任务。同步任务指的是在主线程上执行,只有当前任务执行完毕才能执行下一个任务。异步任务是指不进入主线程,而进入"任务队列"的任务。当异步任务有了运行结果,在可运行的异步任务添加到执行栈中
所以我们要明白以下几点:
- js分为同步任务和异步任务
- 同步任务都在主线程中形成,形成一个执行栈
- 异步任务会存放在任务队列中,当异步任务有了运行结果,就放在任务队列中放置一个事件
- 当执行栈中所有的同步任务执行完毕之后,就会去执行任务队列中放置的任务
- 以上过程会重复执行
宏任务
(macro)task也就是宏任务:我们可以认为每次在执行栈中执行的代码就是一个宏任务
(macro)task包括:script(整体代码)、setTimeOut、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js环境)
微任务
microtask也就是微任务:当前task结束之后立即执行的任务。这些任务响应速度比setTimeout会更快,因为无需等待渲染。
microtask主要包括:Promise.then、MutaionObserver、process.nextTick(Node.js环境)
事件循环
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这个运行机制叫事件循环
在事件循环中,每一次循环成为一个tick,每次tick任务处理模型比较复杂,但是关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行中遇到了微任务,就放到微任务队列中
- 宏任务执行完毕之后,去微任务队列中依次执行所有的微任务
- 当前宏任务执行完毕之后,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕之后,js线程继续接管,开始下一个宏任务
Promise和async
Promise和async的异步体现在then和catch中,所以写在Promise和async中的代码会立即执行
async本身就是promise+generator的语法糖。所以await后面的代码就是microtask
async function async1() {
console.log('async1 start');
await async2();
}
等价于
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(() => {
console.log('async1 end');
})
}
代码分析
1.首先将整个代码(script)压如执行栈
微信图片_20190311100100.png
2.然后定义了两个async方法,接着是一个console,将console压栈,然后输出script start 微信图片_20190311100535.png
3.接着遇到setTimeout,这是一个宏任务
微信图片_20190311101036.png
4.接着执行async1,async1中的第一个console直接入栈,输出async1 start,然后执行await后面的async2,输出async2,await之后的代码就是一个微任务 微信图片_20190311102424.png
5.下面遇到promise,promise的异步体现在then中,所以promise中的代码立即压栈,执行console,输出promise1,将then中的代码放入到微任务中,然后执行最后一句console,输出script end
微信图片_20190311103238.png
6.这时候第一宏任务已经全部执行完毕,此时我们执行微任务队列中的两个console,依次输出async1 end 和promise2
微信图片_20190311103708.png
7.然后执行下一个宏任务,也就是setTimeout,输出setTimeout
微信图片_20190311103908.png
参考文献:
1.https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7
2.http://www.ruanyifeng.com/blog/2014/10/event-loop.html
网友评论