0%

前端知识点巩固(一)

接下来我将会把前端知识点进行滚动复习,预计前端知识点巩固共有五篇。
目的为夯实基础,梳理知识脉络。

如何理解EventLoop——宏任务和微任务篇

宏任务(MacroTask)引入

大部分的任务都是在主线程上执行,常见的任务有:

  • 渲染事件
  • 用户交互事件
  • js脚本执行
  • 网络请求、文件读写完成事件等

为了让这些事件有条不紊地进行,JS引擎需要对之执行的顺序做一定的安排,V8 其实采用的是一种队列的方式来存储这些任务, 即先进来的先执行。模拟如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool keep_running = true;
void MainTherad(){
for(;;){
//执行队列中的任务
Task task = task_queue.takeTask();
ProcessTask(task);

//执行延迟队列中的任务
ProcessDelayTask()

if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
break;
}
}

这里用到了一个 for 循环,将队列中的任务一一取出,然后执行,这个很好理解。但是其中包含了两种任务队列,除了上述提到的任务队列, 还有一个延迟队列,它专门处理诸如setTimeout/setInterval这样的定时器回调任务。

普通任务队列和延迟队列中的任务,都属于宏任务。

微任务(MicroTask)引入

对于每个宏任务而言,其内部都有一个微任务队列

其实引入微任务的初衷是为了解决异步回调的问题。想一想,对于异步回调的处理,有多少种方式?总结起来有两点:

  • 1.将异步回调进行宏任务队列的入队操作。
  • 2.将异步回调放到当前宏任务的末尾。

采用第一种方式,执行回调的时机应该是在前面所有的宏任务完成之后,倘若现在的任务队列非常长,那么回调迟迟得不到执行,造成应用卡顿
为了规避这样的问题,V8 引入了第二种方式,这就是微任务的解决方式。在每一个宏任务中定义一个微任务队列,当该宏任务执行完成,会检查其中的微任务队列,如果为空则直接执行下一个宏任务,如果不为空,则依次执行微任务微任务执行完成才去执行下一个宏任务。

常见的微任务有:

  • MutationObserver
  • Promise.then(或.reject)
  • 以 Promise 为基础开发的其他技术(比如fetch API),
  • V8 的垃圾回收过程。

理解EventLoop——浏览器篇

上demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log('start');
setTimeout(() => {
console.log('timeout');
});
Promise.resolve().then(() => {
console.log('resolve');
});
console.log('end');

/*
1. 执行宏任务,同步代码直接执行,打印 start ,end
2. 将setTimeout放到宏任务队列。
3. Promise.then放入微任务队列。此时当前宏任务队列执行完了。
检查微任务队列,并依次执行。打印 resolve
4. 检查宏任务队列,里面有setTimeout,打印timeout.
结果:start -> end -> resolve -> timeout
*/

这样就带大家直观地感受到了浏览器环境下 EventLoop 的执行流程。不过,这只是其中的一部分情况,接下来我们来做一个更完整的总结。

  • 一开始整段脚本作为第一个宏任务执行
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完出队,检查微任务队列,如果有则依次执行,直到微任务队列为空
  • 执行浏览器 UI 线程的渲染工作
  • 检查是否有Web worker任务,有则执行
  • 执行队首新的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
});
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0);
console.log('start');

// start Promise1 setTimeout1 Promise2 setTimeout2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
console.log('script start')
new Promise((resolve, reject) => {
console.log('async2 end')
// Promise.resolve() 将代码插⼊微任务队列尾部
// resolve 再次插⼊微任务队列尾部
resolve(Promise.resolve())
}).then(() => {
console.log('async1 end')
})
// async function async1() {
// await async2()
// console.log('async1 end')
// }
// async function async2() {
// console.log('async2 end')
// }
// async1()

setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')