浏览器

架构

单进程 -> 多进程 -> SOA

多进程

SOA:解决多进程的资源浪费问题

从输入 URL 到页面展示完整流程

在进行渲染进程的准备上,默认每个标签对应一个渲染进程。但如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点(根域名相同、协议相同)的话,那么新页面会复用父页面的渲染进程

渲染流程

DOM树 + CSS规则树 -> 渲染树 -> 布局(Layout) -> 绘制(Painting) -> 合成

即关键渲染路径JavaScript/CSS --> Style --> Layout --> Paint --> Composite

  1. 构建DOM树:将下载的 HTML 经由解析器解析,最终输出树状结构的 DOM
  2. 样式计算:计算出 DOM 节点中每个元素的具体样式
    1. 将CSS转为浏览器能够理解的结构,也就是 styleSheets
    2. 标准化样式表中的属性值
    3. 根据styleSheets 将样式应用到DOM上
  3. 布局阶段:计算出 DOM 树中可见元素的几何位置
  4. 分层:为实现注入3D变换、Z轴排序等效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树
    1. 拥有层叠上下文属性的元素会被提升为单独的一层
    2. 需要剪裁(clip)的地方也会被创建为图层
  5. 图层绘制:把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表
  6. 栅格化:将图层划分为图块(tile),合成线程会按照视口附近的图块来优先生成位图
  7. 合成与显示:有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到显存中,最后再将内存显示在屏幕上

合成操作是在合成线程上完成的,这也就意味着在执行合成操作时,是不会影响到主线程执行的。这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因

DOM与样式表生成布局树

生成图层树

绘制列表

含有 JavaScript 文件和 CSS 文件页面的渲染流水线

相关概念

对于在交互过程中的性能优化,一个大的原则就是让单个帧的生成速度变快:

  1. 减少 JavaScript 脚本执行时间
    1. 分解执行过久的函数
    2. 采用 Web Workers,但 Web Workers 中没有 DOM、CSSOM 环境
  2. 避免强制同步布局
    1. 强制同步的话,也就是我们说的DOM读写不分离,就会让DOM立刻改变,因为他需要读取DOM的改变
  3. 避免布局抖动
    1. 和强制同步布局一样,都是尽量不要在修改 DOM 结构时再去查询一些相关值
  4. 合理利用 CSS 合成动画
    1. 合成动画是直接在合成线程上执行的,这和在主线程上执行的布局、绘制等操作不同
  5. 避免频繁的垃圾回收 STW

垃圾回收

栈内存回收

20221013162435

堆内存回收

跟JVM的垃圾回收理论有许多相通之处

V8 中也会把堆分为新生代和老生代两个区域,两个区域分别由副、主垃圾回收器进行回收

为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,让垃圾回收阶段与正常JS执行交替执行

20221013163714

代码编译与执行

20221013164947

页面循环系统

while(running) {
   const task = getTaskFromQueue();
   task.run();
}

20221014152719

宏任务微任务

宿主发起的任务称为宏观任务,时间粒度比较大,执行的时间间隔是不能精确控制的

JavaScript 引擎发起的任务称为微观任务

有一个独立的线程执行事件循环不断消费宏观任务队列的任务,Promise 还会产生异步代码,JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列

执行主线程宏任务-->执行所有的微任务,直到清空微任务的队列-->执行一个宏任务--->执行微任务(因为这时候可能又产生了新的微任务)--一个宏-->所有微,然后一直循环往复直到结束

为了实现对任务的优先级处理,就可以把一些优先级高的任务插入到微任务队列

为了规避某些任务执行过长导致整体阻塞的问题,JavaScript 可以通过回调功能也就是让任务滞后执行来强制规避阻塞

setTimeout

while(running) {
   const task = getTaskFromQueue();
   task.run();
   runDelayTask();
}

延迟任务的实现就是在宏任务执行完成之后,从延迟任务队列取任务进行执行,所以这就会导致如果宏任务里面的任务执行比较久,延迟任务也会相应的延迟

setTimeout 存在嵌套调用被嵌套调用 5 次以上的情况,系统会判断该函数方法被阻塞了,那么系统会设置后续的调用最短时间间隔为 4 毫秒

function cb() { setTimeout(cb, 0); }
setTimeout(cb, 0);

为了优化后台页面的加载损耗以及降低耗电量,未激活的页面,setTimeout 执行最小间隔是 1000 毫秒

大部分浏览器都是用32位整数存储延迟值,所以当大于 2147483647 时就会溢出变成 0

虚拟DOM

React 的核心算法是 reconciliation,新的算法称为 Fiber reconciler,其利用了协程的特性,可以让出调度,避免 diff 算法过久造成页面卡顿

虚拟 DOM 主要是一个抽象层,用以描述视图,除了可以解决频繁更新真实 DOM 带来的性能问题,还可以作为一种中间描述,实现跨端