浏览器有多个线程:JS引擎线程、GUI渲染线程、http请求线程、事件处理线程、定时器触发线程。其中JS引擎线程和GUI渲染线程是互斥的。
GUI渲染线程主要工作内容
- 解析html文档生成DOM
- css代码转换为cssom (css object model)
- 结合DOM和CSSOM生成渲染树
- 生成布局(layout)
- 将布局绘制(paint)在屏幕上
屏幕刷新频率和GUI渲染线程
GUI线程渲染的结果不会立刻呈现在屏幕上,等屏幕刷新时才会呈现出来。屏幕刷新频率一般60HZ,即16.6ms刷新一次屏幕。
GUI渲染线程调度
事件循环请参考浏览器事件循环机制。在下次宏任务执行前,GUI渲染线程开始工作,对页面进行渲染。即
// 宏任务 --> 所有微任务 --> 渲染(如果时间片到了) --> 宏任务...
例子1:
document.body.style = "background:blue";
console.log(1);
setTimeout(function fn() {
document.body.style = "background:black";
console.log(2);
}, 0);
执行过程:
- script作为宏任务执行,打印1;
- 渲染,背景蓝色;
- 执行宏任务fn,打印2;
- 渲染,背景黑色;
例子2:
let ul = document.getElementById("root");
setTimeout(function fn111() {
let li = document.createElement("li");
li.innerText = 11111;
ul.appendChild(li);
}, 0);
setTimeout(function fn2222222() {
let li = document.createElement("li");
li.innerText = 2222222222222;
ul.appendChild(li);
}, 0);
执行过程:
- script作为宏任务执行,定时器触发线程把fn111, fn2222222放入宏任务队列
- 执行宏任务fn111
- 时间片没到,继续执行fn2222222
- 渲染
下面是chrome performance Main 线程图,黄色块分别是fn111, fn2222222,紫色块是Layout,绿色是paint

调用栈.png
例子3:
let ul = document.getElementById("root");
setTimeout(function fn111() {
console.time(1);
let li = document.createElement("li");
li.innerText = 11111;
ul.appendChild(li);
for (let index = 0; index < 200; index++) {
console.log(1);
}
}, 0);
setTimeout(function fn2222222() {
let li = document.createElement("li");
li.innerText = 2222222222222;
ul.appendChild(li);
// 模拟耗时任务,大约16ms
for (let index = 0; index < 1; index++) {
console.log(1);
}
}, 0);
执行过程:
- script作为宏任务执行,定时器触发线程把fn111, fn2222222放入宏任务队列
- 执行宏任务fn111
- 时间片到了,渲染
- 继续执行fn2222222
- 渲染
下面是chrome performance Main 线程图,黄色块分别是fn111, fn2222222,紫色块是Layout,绿色是paint。

执行栈.png
总结
js引擎线程和GUI渲染线程是互斥的;
js引擎线程和GUI渲染线程的调度顺序:宏任务 --》 所有微任务 --》16.6ms的时间片到了调度渲染线程 --》宏任务...