开局一张图,过程全靠编
您信吗,.........好了,回归正题
背景:
最近在看js的执行机制时候,联想到很多问题,大家都知道:
- 浏览器是多线程的,JS是单线程的,浏览器在解析的时候只会分配一个线程来执行js
- 在浏览器中打开一个页面就占用了一个进程,加载页面的时候,浏览器分配一个线程去计算DOM树,分配其它的线程去加载对应的资源文件...再分配一个线程去自上而下执行JS
需要知道的几个问题:
- 自上而下执行JS:意思就是一个js文件,从上往下依次执行
- js的线程始终只有一个
- 同步:在一个线程上(主栈/主任务队列)同一个时间只能做一件事情,当前事情完成才能进行下一个事情(先把一个任务进栈执行,执行完成,在把下一个任务进栈,上一个任务出栈...)
- 异步:在主栈中执行一个任务,但是发现这个任务是一个异步的操作,我们会把它移除主栈,放到等待任务队列中(此时浏览器会分配其它线程监听异步任务是否到达指定的执行时间),如果主栈执行完成,监听者会把到达时间的异步任务重新放到主栈中执行...
js的队列类型
只要是异步执行的代码,就会放在队列任务里面,同时js的任务队列有两种类型,下面就用一张经典的图表示:
任务队列执行的优先级:
- 当同步任务执行完成后,此时js线程有了空闲,就去执行任务队列
- 优先执行微队列,再执行宏队列
- 在每次去执行一个宏队列任务时,会遍历当前的微队列,如果微队列有任务,优先执行
为了验证如上结论,我们看看如下代码:
setTimeout(()=>{
console.log('我是延迟0ms的第一个的异步任务')
Promise.resolve('我是一个在异步任务里的微队列,我会在执行宏任务的时候,先执行').then(res=>{
console.log(res)
})
})//默认是0ms,
setTimeout(()=>{
console.log('我是延迟0ms的第二个异步任务')
})
Promise.resolve('我是一个在同步任务里的微队列,我会在执行同步任务完成之后,先执行').then(res=>{
console.log(res)
})
console.log('我是同步任务')
输出结果如下:
执行思路:
1.先执行同步代码,打印出--->我是延迟0ms的第二个异步任务
2.执行微队列代码,打印出--->我是一个在同步任务里的微队列,我会在执行同步任务完成之后,先执行
3.执行宏任务,此时执行第一个宏任务,发现里面居然有promise这个微任务,只能先放在微任务队列里
4.执行第二个宏任务,此时结果却执行了刚刚放入的微任务,验证在每次执行宏任务时候先去遍历微任务执行的结论
好了,相信你看了这个之后了解了微队列和宏队列的执行了,下面我们在来说说宏队列的在面试时候会问到的例子
请看下面代码:
setTimeout(() => {
console.log('第一个宏任务');
}, 200);
setTimeout(() => {
console.log('第二个宏任务');
}, 100);
console.time('WHILE');
let i = 0;
while (i <= 999999999) {
i++;
}
console.timeEnd('WHILE');
setTimeout(() => {
console.log('第三个宏任务');
}, 10);
console.log('同步任务');
输出结果:
执行思路:
代码自上而下执行,碰到第一个程序,先放入主栈(主任务队列),此时浏览器发现这是一个宏任务定时器,把它移出主栈,放入等待任务队列,再继续执行下面的代码,放入主栈执行,发现第二个任务也是宏任务的定时器,放入等待队列,继续往下执行,推入主栈,同步任务,循环99999999次之后输出次数,再执行下一个程序,也移入等待队列,再执行代码,发现是同步任务,输出同步任务,此时主栈空闲,任务队列到达时间后先进先出的原则,首先第二个任务到达时间,把它放入主栈执行,输出第二个宏任务,接着输出第二个宏任务,也许你会疑惑,明明第三个宏任务的延迟最少,却还没输出,是因为此时第三个宏任务是等待951ms后才放入的等待队列,所以放入的时候,前面两个任务已经到了任务时间,所以第三个宏任务最后输出。
好了,我觉得这两个例子已经很能说明问题了,当然可能还有很多其他场景,只要记住执行机制,照样知道它的执行顺序
如果有问题,欢迎留言交流