事件循环

浏览器进程模型

一个进程有很多线程,线程是干活的人,所以一个进程至少一个线程

浏览器极其复杂,拥有多个进程

  1. 浏览器进程

    负责:界面显示、交互、子进程管理

  2. 网络进程

  3. 渲染进程

    会开启一个渲染主线程,负责执行html\css\js代码

    默认每个标签开启一个新渲染进程

渲染主线程

事件循环就发生在其中

  • 解析html
  • 解析css
  • 计算样式
  • 布局
  • 图层处理
  • 60帧
  • 执行全局js代码
  • 执行事件处理函数
  • 执行计时器回调

image-20231009142935167

渲染主线程任务如此繁重,要怎么调度任务呢?

事件循环

单线程是异步产生的原因,事件循环是异步的实现方式

  1. 一开始,渲染主线程进入一个死循环
  2. 每次循环检查消息队列中有没有任务,有任务渲染主线程就取走执行
  3. 其他所有线程可以随时向消息队列添加任务在末尾

但是遇到异步任务怎么办?

如何理解js异步?

答: js是单线程语言,因为他运行在渲染主线程中,而渲染主线程只有一个,且承载着诸多任务,如果采用同步,则其他任务被阻塞。

所以js采用异步来避免这种问题。遇到异步任务时,自己结束任务转而执行后续任务,交给其他线程去处理。其他线程完成异步任务时,把回调函数包装成任务加入到消息队列末尾。

优先级

任务没有优先级,但是消息队列有优先级

任务有类型之分,同种一任务必须在同一消息队列

浏览器必须准备一个微队列,微队列优先级最高

​ 交互队列, 优先级高 ——(事件处理)

​ 延时队列, 优先级中 —— (计时器)

image-20231009175116866

##js计时器精准吗?

不精准,因为:1. js实际调用操作系统的时间。 2. 受到事件循环影响,计时器只能在渲染主线程空闲时运行

在浏览器上的事件循环

线程

微任务一般比宏任务先执行,并且微任务队列只有一个,宏任务队列可能有多个。另外我们常见的点击和键盘等事件也属于宏任务

  • 常见宏任务

    • js主进程
    • setTimeout()
    • setInterval()
    • setImmediate()
    • requestAnimationFrame()
    • postMessage()
  • 常见微任务

    • promise.then .catch .finally
    • async/await
    • Generator函数
    • new MutationObserver()
    • process.nextTick()

案例

1. await

  • await 123: 转化为 await Promise.resolve(123)
  • await express : express一旦状态转为fulfilled,然后执行完当前代码,再把等待的代码代码推到微队列
  • await syncExpress: === 没有await的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function asy1() {
console.log(1);
await asy2();
console.log(2);
}
asy2 = async () => {
await setTimeout(() => {
Promise.resolve().then(() => {
console.log(3);
})
console.log(4);
}, 0);

}
asy3 = async () => {
Promise.resolve().then(() => {
console.log(5);
})
}
//执行
asy1();
console.log(6);
asy3();
1
2
3
4
5
6
7
8
9
10
11
output: 1,6, 
微任务: say2完成,5
宏任务:定时器,
===至此全局js执行完成===
...
===定时器函数到时===
output:1,6,
微任务:5,2,
宏任务:定时器
===asy1完成===
output:1,6,5,2,4, 3

2. return Promise.resolve()

1
2
3
4
5
6
7
8
9
10
11
12
13
Promise.resolve().then(()=> {
console.log(0)
return Promise.resolve(4)
}).then((res)=>{{
console.log(res)
}})
Promise.resolve().then(()=> {
console.log(1)
}).then(()=>{
console.log(2)
}).then(()=>{
console.log(3)
})
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
31
32
33
34
// 开始
微队列:
()=> { ; 1
console.log(0)
return Promise.resolve(4)//
}
// ===以上同步代码执行完成===
// ===开始执行第一个微队列任务===
output:
0
微队列:
1 .then(()=> 函数完成)
// ======
output:
0 1
微队列:
.then(()=> 函数完成) 2
// ======
output:
0 1
微队列:
2 函数完成return出去
// ======
output:
0 1 2
微队列:
函数完成return出去 3
// ======
output:
0 1 2
微队列:
3 4
// ===最后输出===
0 1 2 3 4

总结

1
2
3
4
5
6
()=>{
return Promise.resolve(4)//断点,执行到这一行时,相当于
/*
把 .then(()=>此函数完成) 加入微队列
*/
}.then(res=>console.log(res))

另外:

1
2
3
如果
return Promise.resolve(4).then(res=>console.log(res))
则把 then(()=>此函数完成) 4 依次加入微队列