1. 浏览器整体架构

现代浏览器不是一个单线程程序,而是多进程、多线程协作。

简化模型:

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
Chromium

├── Browser Process

│ 管理浏览器本身:
│ - 标签页
│ - 窗口
│ - 导航
│ - 权限

├── Network Service

│ 提供网络能力:
│ - DNS
│ - TCP
│ - TLS
│ - HTTP
│ - Socket

├── GPU Process

│ 负责 GPU 相关工作

└── Renderer Process

负责网页运行

├── Runtime
├── Blink
├── V8
└── Rendering Pipeline

2. Renderer Process 内部模型

Renderer 是网页真正运行的位置。

可以理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
Renderer Process


Runtime

|
+------+------+
| |
↓ ↓

Blink V8

Web平台能力 JavaScript执行

Runtime 是什么?

Runtime 可以理解为:

网页运行时的协调系统。

它负责:

  • 调度任务
  • 管理事件循环
  • 协调 Blink 和 V8
  • 控制什么时候执行 JavaScript
  • 控制什么时候进入渲染流程

它不是:

  • HTML解析器
  • JS执行器
  • DOM实现

而是:

控制这些模块如何协作。


3. Blink 和 V8 的职责

V8

负责:

JavaScript语言本身。

例如:

1
2
3
4
5
Promise.resolve()

Array.map()

let a = 1

这些属于 ECMAScript。

流程:

1
2
3
4
5
6
7
8
9
JavaScript



V8



执行

负责:

浏览器提供的 Web 平台能力。

例如:

1
2
3
4
5
6
7
8
9
document.querySelector()

fetch()

setTimeout()

EventSource()

WebSocket()

这些不是 JavaScript 自带。

而是浏览器提供。


4. Binding(绑定层)

Binding 是:

JavaScript 世界和浏览器 C++ 实现之间的桥梁。

例如:

1
document.querySelector("#app")

流程:

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
JavaScript



V8执行调用



Binding



Blink C++实现



DOM查询



返回结果



Binding包装



V8返回JS对象

所以:

V8 不懂 DOM。

V8 只知道:

调用了一个函数。

真正修改 DOM:

是 Blink。


5. Web API 和 JavaScript API 区别

判断方法:

问:

这个能力是不是 ECMAScript 标准?


JavaScript 自带

例如:

1
2
3
4
5
6
Promise
Array
Map
Set
JSON
Math

流程:

1
V8

浏览器提供

例如:

1
2
3
4
5
6
7
8
9
document

fetch

setTimeout

EventSource

requestAnimationFrame

流程:

1
2
3
4
5
6
7
8
9
V8



Binding



Blink

6. Event Loop 的职责

Event Loop:

不是执行 JavaScript。

它负责:

决定什么时候让 JavaScript 获得执行机会。

模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
Task Queue



Event Loop



V8



执行JS

7. Task 和 Microtask

Task

来源:

  • 用户事件
  • setTimeout
  • 网络事件
  • SSE事件

例如:

1
2
setTimeout(()=>{
},0)

流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
V8执行setTimeout



Blink Timer注册



时间到



Task Queue



Event Loop



V8执行回调

Microtask

来源:

V8。

例如:

1
Promise.then()

流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
V8



Microtask Queue



当前Task结束



Microtask Checkpoint



执行

8. 为什么 Promise 比 setTimeout 快?

因为:

执行顺序:

1
2
3
4
5
6
7
8
9
当前Task



Microtask清空



下一轮Task

例如:

1
2
3
4
5
6
7
8
9
10
11
12
console.log(1)

setTimeout(()=>{
console.log(2)
})

Promise.resolve()
.then(()=>{
console.log(3)
})

console.log(4)

结果:

1
2
3
4
1
4
3
2

原因:

Promise:

进入 Microtask。

setTimeout:

进入 Task。


9. Scheduler 的作用

Scheduler:

可以理解为:

浏览器的任务调度经理。

它负责:

  • 哪些任务优先
  • 什么时候处理输入
  • 什么时候给渲染机会

但是:

它不能抢占 JavaScript。


例如:

1
while(true){}

执行:

1
2
3
4
5
V8



占用主线程

此时:

等待:

1
2
3
4
5
6
7
Timer Task

SSE Task

Render

Promise

都不能执行。


原因:

JavaScript:

Run To Completion

一个任务必须执行结束。


10. Rendering Pipeline

浏览器显示页面:

不是修改 DOM 就立即显示。

流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
JavaScript修改DOM



Style



Layout



Paint



Composite



GPU显示

11. requestAnimationFrame

requestAnimationFrame:

不是 Timer。

它属于:

浏览器渲染调度。

代码:

1
2
requestAnimationFrame(()=>{
})

流程:

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
V8执行调用



Binding



Blink记录callback



等待下一次Rendering Opportunity



执行callback



Layout



Paint

rAF 和 16.6ms

60Hz屏幕:

一帧:

约:

1
16.6ms

但是:

rAF不是:

剩多少时间执行。

而是:

下一次浏览器准备渲染时执行。


如果:

JS执行太久:

例如:

1
while(true){}

那么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
16ms到了



想渲染



JS还没结束



错过这一帧



等待下一次机会

12. Microtask 为什么可能饿死渲染?

例如:

1
2
3
4
5
6
function loop(){
Promise.resolve()
.then(loop)
}

loop()

流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Microtask



产生Microtask



继续执行



产生Microtask



无限循环

结果:

1
2
3
4
5
Render



永远没有机会

原因:

浏览器规定:

Microtask Queue 必须清空。


13. 性能优化核心思想

浏览器不是帮你抢占 JS。

开发者需要主动让出执行权。

错误:

1
2
3
for(let i=0;i<10亿;i++){

}

优化:

拆分:

1
setTimeout(work,0)

或者:

1
requestAnimationFrame(work)

或者:

使用框架 Scheduler。


14. 最终完整模型

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Browser Process


|
|



Renderer Process


|



Runtime


|
+----------------+
| |
↓ ↓

Scheduler Blink

| |
| |
↓ ↓

Event Loop Web API


|



V8


|



JavaScript


|



Microtask


|



Rendering Opportunity


|



requestAnimationFrame


|



Style



Layout



Paint



Composite