1.浏览器中的线程有以下几种:
- js执行线程
- GUI渲染线程
- 事件监听线程
- 计时器计时线程
- 网络通信线程
2.如何理解js单线程?
之所以说js单线程,是因为它的执行引擎只有一个线程,并且不会在执行期间开启新的线程。而并非是指浏览器单线程。
3.js执行栈
js线程执行任务时,会创建js执行栈。同步任务直接推入执行栈中执行,异步任务推入事件队列中,注册回调函数。当执行栈空闲时,js会去事件队列中读取事件当初注册的回调函数,到执行栈中,并执行函数。
4.事件队列中异步任务划分
异步任务分为宏任务和微任务。
- 宏任务:script, settimeout,ajax请求,dom事件
- 微任务:Promise.then、MutationObserver、process.nextTick()
当宏任务执行完,执行栈空闲时,js首先会将队列中的所有微任务执行完,再去执行宏队列中的宏任务。如此反复,就是事件循环了。
一轮循环是这样的:是一次宏任务加上当前所有微任务执行。当js执行时,先执行宏任务(其实,就是当前宏任务中所有的同步任务),遇到微任务,则推入微队列,并注册回调函数。当该次宏任务执行完后,会读取微任务注册的回调函数到执行栈中执行。执行完后。继续下一轮循环。
看下面这个示例:
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
结果:
// one
// two
// three
分析:
-
首先整段代码可以看做是
宏任务
, -
当js执行时,遇到
setTimeout
,它是宏任务,则放入宏队列,等待本轮执行完后,在一轮循环中执行。然后遇到了promise的then,则被推入微队列。 -
接着,打印
one
。好了,到此,本轮宏任务已经执行完毕,但是,本次循环并未结束。 -
然后,js去微队列中读取then注册的回调函数执行,打印
two
。 -
注意了!!!到此,本轮所有任务执行完毕,本次事件循环结束了。
-
接着,开启下一轮循环,执行settimeout,打印
three
。所以,输出结果为:
one
two
three
再看个示例:
console.log('1');
setTimeout(function () {
console.log('2');
process.nextTick(function () {
console.log('3');
})
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5')
})
})
process.nextTick(function () {
console.log('6');
})
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8')
})
setTimeout(function () {
console.log('9');
process.nextTick(function () {
console.log('10');
})
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12')
})
})
输出如下:
大家可以根据上一个例子的分析模式,分析看看。
最后
当promise的then()和process的nextTick()处在同一个宏任务中时,nextTick()先于then()执行。
示例:
new Promise(function (resolve) {
console.log(3);
resolve();
console.log(4);
}).then(function () {
console.log(5);
});
process.nextTick(function () {
console.log(8);
});
process.nextTick(function () {
console.log(7);
});
输出: