跳到主要内容

浏览器原理✅

浏览器架构及主要组成部分?

答案

参考 浏览器的工作方式

浏览器核心组成部件如下图

  1. 界面:包括地址栏、后退/前进按钮、书签菜单等。浏览器界面的每个部分,但显示请求网页的窗口除外。
  2. 浏览器引擎:在界面和渲染引擎之间协调操作。
  3. 渲染引擎:负责显示请求的内容。例如,如果请求的内容是 HTML,则呈现引擎会解析 HTML 和 CSS,并在屏幕上显示解析的内容。
  4. 网络:对于 HTTP 请求等网络调用,在平台无关接口后面为不同平台使用不同的实现。
  5. 界面后端:用于绘制组合框和窗口等基本 widget。此后端公开的接口并非平台专用。在底层,它使用操作系统界面方法。
  6. JavaScript 解释器:用于解析和执行 JavaScript 代码。
  7. 数据存储:这是持久层。浏览器可能需要在本地保存各种数据,例如 Cookie。浏览器还支持 localStorage、IndexedDB、WebSQL 和 FileSystem 等存储机制。 浏览器的主要组成部分是什么

整个进程模型参考 https://www.chromium.org/developers/design-documents/multi-process-architecture/

  • Borwser Process 主进程 管理渲染进程和其他进程
    • Main Thread 主线程,管理渲染进程
    • IO Thread IO线程,处理网络请求
  • Renderer Process 渲染进程,负责渲染页面
    • Main Thread 渲染进程的主线程,负责解析HTML、CSS和JavaScript
    • Render Thread 工作线程,处理后台任务等
提示

这是一个概念上的示意图,实际上 chrome 还包含插件进程、GPU 进程等其他进程,此外也可通过配置等控制进程执行模式。

延伸阅读

常见浏览器内核及区别?

答案
内核概述
Blink由Google和Opera Software共同开发,基于WebKit内核,是Chrome浏览器和Opera浏览器的渲染引擎。
WebKit由苹果公司开发,为Safari浏览器所用。Kit内核在HTML、CSS和JavaScript处理方面都表现出色,支持的CSS特性较多。
Gecko由Mozilla开发,主要用于Firefox浏览器。
Trident由Microsoft开发,主要用于Internet Explorer浏览器,也是Windows系统自带的默认浏览器内核。从 2018 微软切换到基于 chromium 的 Blink 内核的 Edge 浏览器后,Trident 内核逐渐被淘汰。

输入 url 到页面渲染全过程?

答案
渲染管道
  1. Loading(加载阶段) 细节参考 network
    1. URL 解析 , 浏览器解析 URL,提取协议、域名、路径等信息。
    2. Cache Check ,检查缓存,判断资源是否新鲜,决定是否重新请求。
    3. DNS, 进行 DNS 查询,解析出服务器 IP 地址。
    4. TCP, 建立 TCP 连接(三次握手)。
    5. TLS, 如果是 HTTPS 协议,进行 TLS 握手,建立安全连接。
    6. HTTP, 发送 HTTP 请求,获取资源。
  2. Parsing(HTML 和资源解析)
    1. DOM Tree 解析 HTML 文档,构建 DOM 树。注意 DOM 树的解析是流式的,浏览器会在解析过程中逐步构建 DOM 树,而不是等到整个文档加载完成再解析,这里的 Tokenizer 在单独线程进行,详情参考 html 文档解析调度
    2. JS Execution 解析并执行 JavaScript,可能会修改 DOM 树或触发其他资源加载。
    3. CSSOM Tree 解析 CSS,构建 CSSOM 树
  3. Style(样式计算) 详见 style
    1. CSSOM 将样式表解析为 CSSOM(CSS 对象模型)。
    2. RenderObject Tree 基于 DOM 节点和 CSSOM 构建渲染对象树。
  4. Layout(布局计算) 详见 layout
    1. Layout 基于 DOMTree 和 CSSOM 计算每个渲染对象的实际位置与尺寸,基于 CSS 视觉格式化模型会拆解不同的渲染元素,比如 LayoutBlock、LayoutInline、LayoutText 等。
    2. LayoutTree 生成整个文档树对应的布局树。新版架构叫做 NGLayout(next generation layout),
  5. Paint(绘制阶段) 将样式化的内容转化为图层绘制命令与位图,完成从逻辑表示到图像数据的过渡。阅读 paint 了解细节
    1. GraphicsLayer Tree 根据是否具有合成需求(如 transformopacitywill-change 等)从 LayoutTree 构建 GraphicsLayer Tree,每个 GraphicsLayer 可单独绘制和合成。
    2. Paint Setup 为每个图层生成绘制命令(Display List,如 SkPicture),这些命令是可复用的中间格式。通过 GPU 命令缓冲区 共享给 GPU
  6. Compositing(合成阶段) 这阶段工作在 compositor 线程,Compsiting 阶段会使用 Slimming Paint 技术来优化合成性能,减少不必要的重绘和重排。你可以通过 layer-borders 查看 chrome 如何切片和分层
    1. Compositing Setup Compositor 线程根据 GraphicsLayer Tree 的结构,生成组合属性树(Compositor Property Tree),用于描述每个图层的变换、透明度等属性。你可以通过 Chrome Layers 工具查看合成树。
    2. Raster 对每个图层进行光栅化,将 Display List 转换为位图数据。光栅化可以在 CPU 或 GPU 上执行,取决于具体实现和配置。生成管线命令光栅化底层使用 skia
    3. Composite 将所有图层位图合成为最终帧, 详见 Compositing
    4. Presentation 合成帧经由 GPU 管线提交至操作系统显示系统(如 Windows DWM 或 macOS Quartz),完成屏幕更新。
  7. Display(显示阶段) 工作在 gpu 进程和显示器,将合成的图像数据发送到显示设备(如屏幕、投影仪等),完成最终的视觉呈现。
    1. Display Output GPU 负责将合成的图像数据发送到显示设备进行渲染

参考 Life of a Pixel 核心管道流程

基于 关键路径 如下图

提示

上面是首次加载流程,页面会由于用户滚动或者其他事件触发更新,浏览器会尽可能实现增量更新。这里需要重点注意 reflow 和 repaint 的区别,reflow 相当于从 layout 管道重新计算布局,而 repaint 则只触发 paint 及后续环节。

此外这里是首帧的刷新,而 blink 引擎内部会基于 vsync 机制来协调渲染帧的更新与显示刷新,确保视觉效果的流畅性。参考 Life of a frame 理解每帧的绘制

对于 Video 资源可以参考 VideoNG、PDF 使用 pdfium 完成渲染

延伸阅读

什么是合成阶段(Compositing)?

答案

通过合成阶段浏览器会将渲染树分解为多个图层(Layer),每个图层可以独立绘制,从而尽可能降低重绘和重排的开销。合成阶段的主要任务包括

  1. 图层划分:将渲染树中的节点划分为多个图层。每个图层可以独立绘制和合成,减少不必要的重绘和重排。这时候会生成 compositor property tree, 比如 transform、opacity、clip-path 、video 等属性会被划分到独立的图层。
  2. 光栅化:将每个图层的绘制命令转换为位图数据。光栅化可以在 CPU 或 GPU 上执行
  3. 合成:将所有图层的位图数据合成为最终的渲染帧。合成可以在 GPU 上执行,利用 GPU 的并行处理能力提高性能。

延伸阅读

什么是光栅化(raster),软件光栅化和 GPU 光栅化的区别?

答案

光栅化是将渲染指令翻译为像素点的过程,主要分为软件光栅化和 GPU 光栅化两种方式。

  1. 软件光栅化:通过 CPU 进行光栅化处理, SKIA 会将渲染指令转换为像素数据,然后把调用指令把像素数组传递给 GPU 执行。
  2. GPU 光栅化:渲染进程会直接把命令写入一个 GPU 命令缓冲区,GPU 直接执行光栅化操作。GPU 光栅化通常比软件光栅化更高效,因为它可以利用 GPU 的并行处理能力。但是在处理文本和复杂矢量图形时,软件光栅化可能更精确。

延伸阅读

DOM/CSSOM/渲染树构建?

答案

核心概念

DOM(文档对象模型)和 CSSOM(CSS对象模型)是浏览器渲染页面的基础数据结构。浏览器会将 HTML 解析为 DOM 树,将 CSS 解析为 CSSOM 树,然后结合两者生成渲染树(Render Tree),用于后续的布局和绘制。

提示

详细解释

  • 解析 HTML 生成 DOM 树:浏览器自上而下读取 HTML,遇到标签就创建节点,嵌套结构形成父子关系,属性和文本也会作为节点挂载。
  • 解析 CSS 生成 CSSOM 树:CSS 解析器将样式表转为规则对象,形成树状结构,便于后续样式计算。
  • 构建渲染树:DOM 树与 CSSOM 树结合,过滤掉 display: none 的节点,仅保留可见元素,生成渲染树。每个渲染树节点都包含 DOM 元素及其最终计算样式。
  • 渲染树用于布局(Layout)和绘制(Paint)阶段,决定元素的几何信息和视觉表现。

CSS选择器从右往左解析原理

浏览器在匹配 CSS 选择器时,从选择器最右侧(即目标元素)开始,逐级向左查找父节点是否匹配,直到根节点或选择器左端。这样可以快速排除不匹配的分支,提升性能。例如 .a .b .c,会先查找 .c,再判断其父节点是否有 .b,依次类推。

代码示例

<div class="a">
<div class="b">
<span class="c">text</span>
</div>
</div>
.a .b .c { color: red; }

浏览器会先定位 .c,再判断其父级是否有 .b,再向上查找 .a,而不是从 .a 开始遍历所有后代。

常见误区与开发建议

  • 误区:以为 CSS 选择器是从左往右解析,导致过度嵌套选择器以期望优化性能,实则适得其反。
  • 建议:减少层级选择器和通配符(*)的使用,优先使用类选择器,提升样式匹配效率。
提示

合理优化选择器结构,能显著提升页面渲染性能,尤其在大型 DOM 树下效果明显。

延伸阅读

事件循环原理、宏任务与微任务?

答案

HTML 规范 event loops 章节。定义的 事件循环处理模型 如下

  1. 任务类型

    1. 任务(task) 规范中没有宏任务这个概念,对于常说的宏任务(macro task) 对应规范的任务(task) 。任务会分为很多类型,通过 Source 区分。常见的任务包括
      1. 用户交互任务(The user interaction task source):如用户交互事件(点击、输入等)。
      2. 渲染任务(Rendering task):浏览器渲染相关的任务。
      3. 网络任务(Networking task):如 XMLHttpRequest、fetch、图片加载等。
      4. 定时器任务(Timer task):如 setTimeout、setInterval 等。
    2. 微任务(microtask) 独立于任务是一个单独的队列,会在每次宏任务处理完毕后立即执行,按照先进先出(FIFO)顺序执行。典型的微任务如下
      1. Promise 微任务,由 V8 控制
      2. MutationObserver 微任务,blink 控制
      3. queueMicrotask 微任务, 浏览器提供的 API,相比采用 Promise, MutationObserver 等方式更语义化, 阅读 解释 queueMicrotask 理解设计初衷
    3. 特殊任务 此外还有一些特殊的任务,也在 Task 的范畴这里单独列出
      1. requestIdleCallback 提供浏览器闲时调度能力
      2. requestAnimationFrame 提供在渲染绘制前执行的能力,多用于动画
  2. 任务执行顺序

    1. 对于任务会按照推入顺序和优先级执行,具体策略由浏览器决定,一般的规则如下,用户事件优先级最高
    2. 每个任务执行完毕,会检查微任务队列是否为空,如果有值,按照微任务的推入顺序执行所有微任务,直到微任务队列清空。注意由于微任务存在清空操作,要避免大量微任务导致 IO 饥饿,此时可以通过 requestIdleCallback 避免此问题
  3. 事件循环 基于上述的任务处理流程存在三种类型的事件循环

    1. 窗口事件循环(window event loop) 也就是我们常说的渲染进程主线程的事件循环,处理用户交互、渲染等任务。
    2. 工作线程事件循环(worker event loop) 处理 Web Worker 中的任务,独立于主线程。
    3. worklet 线程(worklet event loop) 处理 Worklet 中的任务,独立于主线程,适用于音频处理、绘图等场景,是一个轻量级的 worker 线程

以上就是规范定义的核心逻辑,但是浏览器的实现会更复杂一些,以 chromium 为例,参考 blink 任务调度说明

  1. 任务类型 浏览器会将任务作进一步细分,可以参考 源码 task_typs 查看细分类型
  2. 任务调度 为了尽可能能避免阻塞渲染,浏览器会优化任务的调度核心设计包括
    1. 任务优先级task_traits 定义了不同的任务类型由高到低
      1. USER_BLOCKING 直接导致页面重新渲染的用户行为,例如滚动,窗口尺寸变化等
      2. USER_VISIBLE 用户可见的任务,例如点击按钮,下载文件,加载图片等
      3. LOWEST 最低优先级的任务,例如日志上报,预加载等
    2. 调度策略 浏览器会根据任务状态和优先级运行环境提供不同调度策略, 对于不同类型任务是否存在如下策略可以参考 task type 表格
      1. 暂停(Pausing) 例如 alert,print,debugger 可以暂定 js 执行(这里是直接阻塞主线程即使是同步任务也能暂定)
      2. 延迟(Deferring) 基于优先级实现延迟调度,典型的包括 requestidlecallback 的闲时判断,触发了多个任务的时候按照优先级处理等
      3. 冻结(Freezing) 在页面处于后台的场景满足特定规则会出现页面冻结,避免不必要的资源消耗,例如安卓设备在后台 5 分钟会触发此逻辑。
      4. 限流(Throttling) 在后台对定时器等事件降低触发频率,避免不必要的资源消耗,例如 setTimeoutsetInterval 在后台情况满足限流条件会延迟为 1 分钟触发一次,即使你设置了更短的时间间隔,详见 throttling in chrome
  3. 闲时调度(Idle Scheduling) 参考 blink 闲时调度 说明, 渲染进程在每帧 commit 给 compositor 线程后和下一帧 willBeginFrame 前,会有一段空闲时间,此时可以执行例如 requestIdleCallback 的任务。js 定时器调度也被优化到了这个阶段,可以参考 调度 js 定时器执行 了解细节

延伸阅读

重排和和重绘及优化?

答案
  1. 重排(reflow) 发生在浏览器重新计算网页的某些部分的位置和几何形状时(例如在交互式站点更新后)。这通常会紧接着重绘(repaint),即浏览器重新绘制网页以显示更新后的视觉效果。
  2. 重绘(repaint) 重绘在浏览器重新绘制网页以显示由 UI 更改引起的视觉更新时发生,例如在交互式站点上进行更新后。这通常是在重排之后发生的,重排是浏览器重新计算网页的某些部分的位置和几何形状。

重排和重绘是导致浏览器性能下降的主要原因之一,这里重点关注重排,因为它比重绘更耗费资源。

参考 What forces layout / reflow 常见触发重排的操作如下

  • 直接修改元素的几何属性,如 widthheighttopleftmarginpaddingborder-width 等。
  • 插入、删除 DOM 节点,或更改节点结构(如 appendChildremoveChildinsertBefore 等)。
  • 读取会触发布局的属性和方法,如 offsetWidthoffsetHeightoffsetTopoffsetLeftclientWidthclientHeightgetBoundingClientRect()getClientRects()
  • 访问滚动相关属性和方法,如 scrollTopscrollLeftscrollWidthscrollHeightscrollTo()scrollBy()scrollIntoView()
  • 设置或获取元素的 focus()select()innerText
  • 读取 window.getComputedStyle(),尤其是访问返回对象的属性时。
  • 修改表单元素的选区(如 input.select()textarea.select())。
  • 读取或设置 SVG 某些属性和方法(如 getBBox()getComputedTextLength())。
  • Canvas2D API 某些操作(如 fillText()strokeText()、设置 directionfilter)。
  • 读取窗口尺寸相关属性,如 window.innerWidthwindow.innerHeightwindow.scrollXwindow.scrollY(部分浏览器实现)。

在错误的触发重排的场景下,通过如下方式优化性能

  1. 批量修改 DOM:将多次 DOM 操作合并为一次操作,减少重排次数。例如,使用 DocumentFragmentcloneNode 批量插入节点。
  2. 使用 CSS 类:通过添加或删除 CSS 类来批量修改样式,而不是逐个修改样式属性。这样可以减少重排次数。
  3. 使用 requestAnimationFrame:将需要重排的操作放在 requestAnimationFrame 回调中执行,这样可以确保在浏览器下一次重绘之前进行布局计算。
  4. 避免频繁读取布局属性:如果需要多次读取布局属性,可以先将其存储在变量中,避免多次触发重排。例如,使用 let rect = element.getBoundingClientRect() 存储布局信息,然后在需要时使用该变量。
  5. 使用 will-change:对于需要频繁更新的元素,可以使用 will-change CSS 属性来提示浏览器提前进行优化,减少重排的开销。
  6. 使用 CSS 动画:尽量使用 CSS 动画而不是 JavaScript 动画,CSS 动画通常会触发 GPU 加速,减少重排的开销。

对于 css 详见 CSSTriggers 讲解哪些 CSS 属性会触发重排和重绘。

延伸阅读

浏览器渲染进程内存结构是怎样的?

答案

延伸阅读

  • Blink GC 讲解 Blink 的垃圾回收机制

浏览器缓存中 Memory Cache 和 Disk Cache, 有啥区别?

答案
  • Memory Cache(内存缓存):资源直接存储在内存中,访问速度极快(纳秒级),但容量有限,通常在刷新或关闭页面后失效。适合当前会话内频繁访问的资源。
  • Disk Cache(磁盘缓存):资源存储在硬盘上,访问速度比内存慢(毫秒级),但容量大,关闭浏览器后依然保留。适合长期存储和较大体积的资源。

核心概念

Memory Cache(内存缓存)和 Disk Cache(磁盘缓存)都是浏览器用来提升资源加载速度的缓存机制,区别在于存储介质和访问速度。

详细解释

  • Memory Cache:资源被直接缓存在内存中,访问速度极快(纳秒级),但容量有限,重启浏览器或刷新页面后通常会失效。适合短期、频繁访问的资源。
  • Disk Cache:资源被缓存在本地硬盘,访问速度比内存慢(毫秒级),但容量大,关闭浏览器后依然保留。适合长期存储、较大体积的资源。

详情参考讨论 浏览器是根据什么决定「from disk cache」与「from memory cache」?