为什么JavaScript是一门单线程语言?作为一门浏览器脚本语言,它的主要用途就是操作DOM和与用户交互设计,如果说js是多线程的话,那么它在操作DOM的时候,一个线程对DOM进行了新增操作,另一个线程对DOM进行了删除操作,那么这个时候js的处理将会变得十分复杂。为了避免这种情况,JavaScript一诞生就是单线程。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。所有任务可以分成两种,一种是同步任务,另一种是异步任务。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;所有同步任务都在主线程上执行,形成一个执行栈;
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
也就是说:当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
主线程在运行的时候,产生堆和栈,栈中的代码调用外部的API,它们会在“任务队列”中加入各种事件。只要栈中的代码执行完毕,主线程就会去读取“任务队列”中的回调函数依次执行。
在任务队列中,其实又分为宏队列和微队列,他们的执行优先级也有区别,那么哪些回调函数放在宏队列,哪些回调函数放在微队列呢?
宏队列:dom事件回调、ajax回调、定时器回调
微队列:promise回调、mutation回调
因此JS执行时首先必须执行所有的初始化同步任务代码,执行完以后,每次准备取出第一个宏任务执行之前,都要将所有的微任务一个一个取出来执行。
让我们看一个案例帮助我们理解,例1:
setTimeout(() => {
console.log('timeout callback()1');
Promise.resolve(3).then(
value => {
console.log('Promise onResolved3()', value);
}
)
}, 0)
setTimeout(() => {
console.log('timeout callback2()');
}, 0)
Promise.resolve(1).then(
value => {
console.log('Promise onResolved1()', value);
}
)
Promise.resolve(2).then(
value => {
console.log('Promise onResolved2()', value);
}
)
上面的代码中写了两个setTimeout定时器函数,在里面写入了打印输出的回调,以及两个状态为resolved的Promise对象,在then方法中写入了两个打印输出的回调,我们已经知道了宏队列和微队列的执行流程,那么我们来分析一下上面代码的执行流程。
'Promise onResolved1()', 1
与'Promise onResolved2()', 2
'timeout callback()1'
'Promise onResolved3()', 3
timeout callback2()
所以最后的打印顺序应该是:
Promise onResolved1() 1
Promise onResolved2() 2
timeout callback()1
Promise onResolved3() 3
timeout callback2()
看完上面这道简单的例子,我们再来看一下第二道比较复杂的题,看看能否判断出正确的输出顺序,例二:
setTimeout(() => {
console.log('0');
}, 0)
new Promise((resolve, reject) => {
console.log('1');
resolve();
}).then(() => {
console.log('2');
new Promise((resolve, reject) => {
console.log('3');
resolve()
}).then(() => {
console.log('4');
}).then(() => {
console.log('5');
})
}).then(() => {
console.log('6');
})
new Promise((resolve, reject) => {
console.log('7');
resolve();
}).then(() => {
console.log('8');
})
分析:
1、从上往下看没有同步代码,首先将定时器中的回调添加到宏任务队列中,所以现在宏任务队列中的任务[0]
2、再执行Promise代码,将先同步代码所以打印1
,然后状态立马变成resolve,所以将其中的异步回调函数打印代码加入到微队列中[2]
3、微队列中2还没有打印取出,所以我们再看下一个Promise中的代码,先将打印同步代码7
,然后立即变为resolve状态,并将异步回调打印8的代码放入微队列中所以现在的微队列是:[2,8]
4、所有的执行完之后我们要先把微队列中的代码都取出执行完再去执行后面的代码以及宏队列的代码,所以先取出2,即打印2
,所以现在的微队列只有一个任务[8]
5、所以现在已经打印了1,7,2
,打印完2以后我们再从这一行代码往下看又new了一个Promise对象,里面有同步代码打印3
,然后立即变为resolve状态,因此将4放入微队列[8,4]
6、接下来这一步要非常注意:在我们没有打印4的时候,那么我们是不会把后面then方法中的5放入微队列中的,我们会先将外层Promise中的then中的6放入微队列,因为内层的Promise已经执行完最后一个then方法了,因此现在的微队列是[8,4,6]
7、现在开始取出微队列中的任务进行执行,将依次打印8
,打印完8后面没有其余代码,因此急需取出打印4的任务再打印4
,当打印完4,之后那么就会将后面then中的打印5的异步任务放入微队列,因此现在的微队列是[6,5]
,目前的打印顺序是1,7,2,3,8,4
。
8、最后我们只需要将微队列中剩余的任务和宏队列中剩余的任务取出执行便分析完了整个顺序流程,因此最后的打印顺序应该是1,7,2,3,8,4,6,5,0
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。