1、js单线程,并非指js只有一个线程,而是同一时刻只能有一个线程在工作。
2、js中,主线程之外,还有其他线程,比如事件循环线程,定时器触发线程,http异步线程, 浏览器事件线程。
3、在js主线程中,分为两个子线程,js引擎线程,GUI渲染线程。这两个线程是互斥的,同一时刻只能执行一个,要么执行js,要么渲染html
4、任务队列中,分为宏任务微任务。每次执行任务队列时,先执行微任务,再执行宏任务。
5、通常宏任务指 setTimeout,setInterval,XMLHttprequest,fetch等回调。 微任务指 Promise,MutationObserver等回调。
6、如果在 定时器触发线程、http异步线程、浏览器事件线程中,没有回调,则不会放入队列中。
7、事件循环线程,必须等待主线程中的同步代码执行结束,才会去任务队列再取一个 任务放入 主线程中执行。
//index.js
console.log('a');
Promise.resolve().then(() => {
console.log('b');
});
setTimeout(() => {
console.log('c');
}, 0);
setTimeout(() => {
console.log('d');
Promise.resolve().then(() => {
console.log('e');
});
}, 0);
console.log('f');
下面是上述代码的执行逻辑:
- 遇到console.log('a'); 同步代码,执行,输出a;
- 遇到Promise,异步代码,放入任务队列。又因为是promise的回调,属于微任务。标记微任务1
- 遇到setTimeout执行,放入定制器触发线程中,定时器触发线程中维护何时倒计时结束,并将回调放入任务队列。又因为setTimeout的回调属于宏任务。标记为宏任务1
- 又遇到setTimeout执行,放入定制器触发线程中,将回调放入任务队列。因为setTimeout的回调属于宏任务。标记为宏任务2
- 遇到console.log('f'); 同步代码,执行,输出f
此时主线程中的同步代码已经完全执行,控制台输出a,f。主线程是空的。此时事件循环线程发现,任务队列有东西,分别是微任务1,宏任务1,宏任务2.
- 按照先执行微任务,再执行宏任务顺序,先将微任务1,即 () => { console.log('b'); } 放入主线程中由js执行。输出b,
- 此时主线程执行完,又空了,此时任务队列还有宏任务1,宏任务2。由于宏任务1先放入的,按照队列的先进先出顺序。先将宏任务1放入主线程。即 () => { console.log('c'); },输出c,
- 再判断队列中是否有微任务,如果有,则全部执行。如果没有,就继续执行宏任务2.
将宏任务2放入主线程,即
() => { console.log('d'); Promise.resolve().then(() => { console.log('e'); }); }
输出d,遇到promise,异步代码,放入微任务队列。标记为微任务2。此时主线程又空了。
- 此时任务队列只有微任务2,没有其他的宏任务和微任务。 最后再执行微任务2。即 () => { console.log('e'); }, 输出e
总结下: 最后输出结果为 a f b c d e
注意:
1、上面的 setTimeout(()=>{}); 属于同步代码,会执行,如果 let timer = setTimeout(()=>{}); 你会发现timer有值,是个数字。但也仅仅是执行 setTimeout后将引用返回,剩下的倒计时和回调。都在定时器触发线程中维护。
2、同样,上面的 Promise.resolve() 也属于同步代码,let p = Promise.resolve() .会发现p有值,是个Promise对象,但也仅仅是执行 Promise.resolve() 后将引用返回,剩下的then中的回调。都在微任务队列中维护
以上是自己通过阅读其他文章,加上自己理解和本地调试之后的 一点点感想。 如有问题,还望指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。