前言
JS是一个单线程的语言,也就是说它同一时间只能执行一段代码。这或许就是为什么喜欢JS的原因吧,JAVA看了头疼。接下来我们从理论到实践来彻底的搞懂JS是怎样的一个单线程。
理论知识
虽然js是单线程但是我们可以将任务分成两类,
- 同步任务:需要执行的任务在主线程上排队,一次执行
- 异步任务:没有立马执行但是需要被执行的任务,放在
任务队列里。并且在需要执行异步任务时,还需要浏览器协助完成,形成一套事件循环机制(event loop)。
异步任务细分
- 宏任务:script(整体代码),
setTimeout,setInterval, setImmediate, I/O, UI rendering。 - 微任务:
Promises, Object.observe, MutationObserver
宏任务、微任务的执行顺序
-
遇到异步宏任务则将异步宏任务放入宏任务队列中,
-
遇到异步微任务则将异步微任务放入微任务队列中,
-
当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
-
微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
-
一直循环直至所有任务执行完毕。
总结就是:同步任务->微任务->宏任务,一直循环到任务队列清空
理论会了,代码就差不多了,开始验证
验证执行顺序
1. 主线程和setTimeout
setTimeout(() => {
console.log("计时器");
let end = +new Date()
console.log("用时" + (end - start) / 1000);
}, 1000)
console.log('开始');
let start = +new Date()
console.log(start);
for (let i = 0; i <= 10000000000; i++) {
}
从这个结果我们可以得出主线程没有执行完毕,setTimeout的回调函数是永远不会触发,从打印的结果可知主线程执行时间已经超过了setTimeout第二个参数设置的timeout时间,那么等运行到该回调函数时,它会忽略掉这个时间,并立即执行。
2. 主线程、setTimeout和promise
setTimeout(() => {
console.log("计时器");
}, 1000)
new Promise((res, rej) => {
console.log("Promise");
res("Promise 成功")
}).then((data) => {
console.log(data);
})
console.log('开始');
for (let i = 0; i <= 10000000000; i++) {
}
分析一下这个结果,因为定义promise的构造部分是同步的,所有先输出Promise,结合我们上面的理论知识,微任务在宏任务前面执行,那么就没有错了。
3. 主线程、setTimeout、promise、process.nextTick
可能有人不知道什么是process.nextTick,简单的说一下,process.nextTick() 是Node.js提供的一个异步执行函数,它不是setTimeout(fn, 0)的别名,它的效率更高,它的执行顺序要早于setTimeout和setInterval,它是在主逻辑的末尾任务队列调用之前执行。上代码 上代码
setTimeout(() => {
console.log("计时器");
}, 1000)
new Promise((res, rej) => {
console.log("Promise");
res("Promise 成功")
}).then((data) => {
console.log(data);
})
process.nextTick(() => {
console.log("nextTick");
})
console.log('开始');
for (let i = 0; i <= 10000000000; i++) {
}
分析下结果啊,主线程->nextTick->微任务->宏任务, 跟我们的理论简直就是一模一样。
稍微难一点的结合
setTimeout(() => {
console.log("计时器1")
}, 0);
new Promise(function (resolve, reject) {
console.log("Promise");
setTimeout(() => {
resolve();
}, 0)
}).then(() => {
console.log("Promise 成功")
}).then(() => {
console.log("Promise 成功")
});
process.nextTick(function () { console.log("nextTick") });
console.log("主线程");
setTimeout(() => {
console.log("计时器2")
}, 0);
分析下结果吧
主线程->nextTick->宏任务->微任务->宏任务,为什么这次是宏任务再到微任务再到宏任务,上面一句话很重要一直循环到任务队列清空,当nextTick执行后,由于微任务队列为空,所以进入宏任务队列执行计时器1,而在执行宏任务时,又有微任务进入微任务队列,所以执行微任务执行Promise 成功,当微任务队列清空,宏任务队列还有任务,最后执行计时器2。
结语
今日分享结束,希望你们懂了,可以去休息了