Skip to content

浏览器渲染进程包含的线程

  1. GUI 渲染线程,负责解析HTML,CSS,构建DOM、CSSOM和render tree, 布局和绘制。当页面回流时。
  2. js引擎线程,一个渲染进程只有一个js引擎线程,负责解析执行js代码
  3. 事件线程,当事件触发时,会添加到队尾,等待js引擎执行
  4. 定时器线程,触发后放在队尾
  5. htto请求线程,请求后放在队尾

简略版xuanran

  1. 解析html,构建dom树
  2. 解析css,构建css树
  3. cssom和dom合并成渲染树
  4. 根据渲染树计算节点位置,布局
  5. 调用GUI绘图,上色,合成图层,并渲染到界面上

html、css和js解析构建顺序

  1. html解析从上到下解析文档,依次构建DOM
  2. 如果碰到link或者style,异步的进行cssom构建
  3. 如果碰到js脚本,会先等到前面的cssom构建完成,才会解析js脚本(可以先下载)。js脚本解析执行期间,html会停止解析
  4. 初始的html解析完成后,会触发documentContentLoad 事件。此时图片资源可能还未下载完成
  5. load 事件表示初始html以及资源全部load

渲染进程中各种线程的配合

js引擎线程会执行一个有多个字队列的队列,队列中每个子队列是由各种引擎推送的的。刚开始时会GUI线程会解析HTML,当碰到js代码会将js推送到js的引擎队列,这时js引擎会立即执行。中断GUI进程的渲染。在代码中js引擎可能会发起请求、定时器或者新建事件绑定,就是通知其他线程开始工作, 定时线程会在定时完成后将代码转移到js引擎的新子队列中。 事件引擎和网络同理

渲染进程多线程

渲染进程和尽可能的启用并行的多线程的方式

  1. 主线程(main thread)

    • 解析html css 构建渲染树
    • 布局和绘制
    • 执行js代码
    • 处理事件
    • 定时器
  2. 合成器线程(compositor thread)

    • 分层 layer
    • 图层分块
    • 合成器
    • 处理简单的动画(不需要由主线程调度)
  3. 光栅线程(raster thread)

    • 根据合成层的数据转换成GPU能够理解的像素数据
  4. 工作线线程(work thread)

    • 各种worker

主线程

通过触发的方式一直运行。有任务队列、用户事件、进程通信、vsync触发,其中vsync触发会间隔周期不断触发。当没有触发时,主线程会休眠。每次触发就是一个task,通过perfermance可以观察到。raf idle callback也认为是一次task

每次触发,js开始执行当前任务、然后执行微任务、然后判断是否需要render(满足至少大于16.6ms),如果需要执行则执行raf,然后会立马检查是否有新的任需要执行,如果没有执行idlecallback(同时idleback还有限制是20ms执行一次,总是在raf同周期后)。

我们可以得出结论, 每次事件循环执行的任务是不一样的,有时候会执行raf、idle,有些时候会执行layout、paint、有些时候纯执行js。

得出结论是如果当前时间循环和上一次循环大于16ms,该次时间循环旧总是会执行raf、idle同时会有layout、paint。

react 为什么不使用settimeout和idle callback

settimeout 至少4ms才触发一次,idle callback至少一个帧才触发一次,太慢了。

js
const button = document.getElementById("myButton");
const div = document.getElementById("myDiv");
let count = 0;

function update() {
  count += 1;
  div.textContent = count;
}

function sleep(iterations) {
  let result = 0;
  for (let i = 0; i < iterations; i++) {
    // 模拟一些复杂的数学运算
    result += Math.sqrt(i) * Math.sin(i);

    // 随机执行一些额外的操作,进一步消耗CPU
    if (i % 10000 === 0) {
      result = result % 10000000;
    }
  }

  return result;
}

const requestAnimationFrameAction = function () {
  // update();
  console.log("requestAnimationFrame executed");
  requestAnimationFrame(requestAnimationFrameAction);
};
requestAnimationFrame(requestAnimationFrameAction);

const requestIdleCallbackAction = function () {
  // update();
  console.log("requestIdleCallback executed");
  requestIdleCallback(requestIdleCallbackAction);
};

requestIdleCallback(requestIdleCallbackAction);

const setTimeoutAction = function () {
  // update();
  sleep(5000000);
  console.log("setTimeout executed");
  setTimeout(setTimeoutAction);
};

setTimeout(setTimeoutAction);

// const channel = new MessageChannel();
// const messageChannelAction = function () {
//   update();
//   channel.port1.postMessage(null);
//   console.log("messageChannel callback executed");
//   Promise.resolve().then(() => {
//     console.log("Promise callback executed");
//   });
// };

// channel.port2.onmessage = messageChannelAction;
// messageChannelAction();

button.addEventListener("click", function onClick() {
  update();
  requestIdleCallback(() => {
    // update();
    console.log("Idle callback executed");
  });
  requestAnimationFrame(() => {
    update();
    console.log("Animation frame callback executed");
  });
  setTimeout(() => {
    update();
    console.log("Timeout callback executed");
  });
});