重新学习前端之浏览器

6 阅读37分钟

浏览器

一、浏览器架构

1. 浏览器架构

定义 浏览器架构是指浏览器内部的整体设计结构,包括各个进程、线程的组织方式,以及它们之间的通信机制。现代浏览器采用多进程架构来提升稳定性和安全性。

原理 现代浏览器主要由以下进程组成:

  • Browser 进程(浏览器主进程):负责界面展示、子进程管理、网络管理、文件访问等
  • Renderer 进程(渲染进程):负责将 HTML/CSS/JavaScript 转换为用户可以交互的网页
  • GPU 进程:负责 3D CSS 绘制、WebGL、Canvas 等硬件加速任务
  • Network 进程(网络进程):负责网络资源加载
  • Plugin 进程(插件进程):负责运行插件(如 Flash)

代码示例

// 在 Chrome 中可以通过 chrome://process-internals/ 查看各进程状态
// 通过 performance API 可以感知多进程架构的影响
console.log(navigator.hardwareConcurrency); // 可用线程数

常见误区

  • 误区一:认为浏览器是单进程的。现代浏览器从 Chrome 开始采用多进程架构
  • 误区二:每个标签页一定对应一个进程。实际上浏览器会根据策略合并同源页面到同一进程

2. 浏览器进程

定义 浏览器进程(Browser Process)是浏览器的核心进程,也称为 UI 进程或主进程。

原理 Browser 进程的主要职责:

  • 管理其他所有进程(创建、销毁、通信)
  • 处理浏览器 UI(地址栏、书签栏、前进后退按钮)
  • 处理与用户交互的非标签页部分
  • 管理 Cookie、书签等持久化数据
  • 处理网络请求的调度

示例 当你在地址栏输入 URL 时,就是 Browser 进程在处理输入,然后委托 Network 进程发起请求,再分配给 Renderer 进程渲染页面。


3. 浏览器线程

定义 浏览器线程是进程内部的工作单元。一个进程可以包含多个线程,线程之间共享内存。

原理 渲染进程中最主要的线程:

  • 主线程(Main Thread):执行 JS、解析 HTML/CSS、计算布局、绘制
  • 合成线程(Compositor Thread):处理图层合成,可以不依赖主线程独立运行
  • 光栅线程(Raster Thread):将绘制指令转换为位图
  • 工作线程(Worker Thread):执行 Web Worker 代码
// 主线程被阻塞的示例
for (let i = 0; i < 1e9; i++) {} // 阻塞主线程,页面无法响应

常见误区

  • 误区:认为 JS 执行和页面渲染在不同线程。实际上它们在同一个主线程中,互斥执行

4. 多进程架构

定义 多进程架构是指浏览器将不同的功能模块运行在独立的进程中,各进程之间通过 IPC(进程间通信)进行数据交换。

原理

Browser Process (UI进程)
  ├── Renderer Process 1 (Tab 1)
  ├── Renderer Process 2 (Tab 2)
  ├── Renderer Process 3 (Tab 3)
  ├── GPU Process
  ├── Network Process
  └── Plugin Process

优势

  • 稳定性:一个渲染进程崩溃不影响其他进程
  • 安全性:可以使用沙箱隔离渲染进程
  • 性能:可以利用多核 CPU,并行处理任务

Chrome 策略(Site Isolation) 从 Chrome 67 开始,默认启用站点隔离(Site Isolation),每个站点(站点定义为 eTLD+1,如 example.com)分配独立的渲染进程。


5. 渲染进程

定义 渲染进程(Renderer Process)是负责将 HTML、CSS 和 JavaScript 转换为用户可以看到的网页的进程。

原理 渲染进程的核心工作:

  1. 解析 HTML 生成 DOM 树
  2. 解析 CSS 生成 CSSOM 树
  3. 合并 DOM 和 CSSOM 生成渲染树(Render Tree)
  4. 执行 Layout(布局/重排)
  5. 执行 Paint(绘制)
  6. 执行 Composite(合成)

渲染进程的核心线程是主线程,负责执行 JavaScript、处理 DOM、计算样式、布局和绘制。


6. GPU 进程

定义 GPU 进程是负责图形硬件加速的独立进程。

原理 GPU 进程的主要职责:

  • 处理 3D CSS 变换
  • 处理 WebGL 渲染
  • 处理 Canvas 2D 加速
  • 处理视频解码加速
  • 处理图层合成
// 使用 GPU 加速的 CSS 属性
.element {
  transform: translateZ(0); /* 创建合成层,使用 GPU 加速 */
  will-change: transform;   /* 提示浏览器使用 GPU 加速 */
}

优势

  • 将图形计算从 CPU 转移到 GPU,提升渲染性能
  • 即使 GPU 进程崩溃,也不会影响其他进程

7. 网络进程

定义 网络进程(Network Process)是负责处理所有网络请求的独立进程。

原理 网络进程的职责:

  • 发起 HTTP/HTTPS 请求
  • 处理重定向
  • 管理缓存
  • 处理 Cookie
  • 处理跨域预检请求(CORS Preflight)

网络进程通过 IPC 与渲染进程通信,将响应数据传递给渲染进程。


8. 插件进程

定义 插件进程(Plugin Process)是运行浏览器插件(如 Flash、PDF 阅读器)的独立进程。

原理

  • 插件进程通过沙箱隔离,防止有漏洞的插件影响浏览器安全
  • 现代浏览器逐渐淘汰 NPAPI 插件,转向 PPAPI 和扩展(Extension)架构
  • Chrome 已内置 PDF 阅读器,不再依赖外部插件

二、渲染原理

9. 浏览器渲染流程

定义 浏览器渲染流程是指从接收到 HTML 到将像素显示在屏幕上的完整过程。

原理

graph LR
    A[HTML] --> B[解析 HTML 生成 DOM 树]
    C[CSS] --> D[解析 CSS 生成 CSSOM 树]
    B --> E[合并生成 Render Tree]
    D --> E
    E --> F[Layout 布局]
    F --> G[Paint 绘制]
    G --> H[Composite 合成]
    H --> I[显示到屏幕]

详细步骤

  1. 构建 DOM 树:浏览器从服务器获取 HTML,从上到下解析,将标签转换为 DOM 节点
  2. 构建 CSSOM 树:解析 CSS,生成 CSSOM 树
  3. 构建 Render Tree:DOM + CSSOM = Render Tree(不包含 display:none 的元素,包含 visibility:hidden)
  4. Layout(布局/重排):计算每个节点在屏幕上的确切位置和大小
  5. Paint(绘制/重绘):遍历渲染树,调用 GPU 将每个节点绘制为像素
  6. Composite(合成):将不同图层合并,最终显示到屏幕

代码示例

<!-- 这段 HTML 的渲染过程 -->
<!DOCTYPE html>
<html>
<head>
  <style>
    .box { width: 100px; height: 100px; background: red; }
    .hidden { display: none; }
  </style>
</head>
<body>
  <div class="box">可见</div>
  <div class="hidden">不可见</div> <!-- 不会出现在 Render Tree 中 -->
</body>
</html>

常见误区

  • 误区一:认为 <script> 标签不影响渲染。实际上默认情况下 <script> 会阻塞 HTML 解析
  • 误区二:认为 visibility: hidden 不参与渲染树构建。实际上它参与构建,只是不可见

10. DOM 树

定义 DOM(Document Object Model)树是 HTML 文档的对象表示,每个 HTML 标签都对应一个 DOM 节点。

原理

<!-- HTML -->
<html>
  <head>
    <title>页面</title>
  </head>
  <body>
    <div id="app">
      <p>Hello</p>
    </div>
  </body>
</html>

对应的 DOM 树结构:

Document
  └── html
       ├── head
       │    └── title
       │         └── "页面"
       └── body
            └── div#app
                 └── p
                      └── "Hello"

关键特性

  • DOM 树是自上而下增量构建的
  • <script> 标签默认阻塞 DOM 构建(除非使用 asyncdefer
  • DOM 节点可以通过 document 对象访问和操作

11. CSSOM 树

定义 CSSOM(CSS Object Model)树是 CSS 样式规则的对象表示。

原理

body { font-size: 16px; }
p { color: blue; }

对应的 CSSOM 树:

CSSOM
  ├── body { font-size: 16px; }
  └── p { color: blue; }

关键特性

  • CSSOM 构建会阻塞渲染(因为需要完整的 CSSOM 才能构建 Render Tree)
  • CSS 加载不会阻塞 DOM 构建,但会阻塞 Render Tree 构建
  • <link rel="stylesheet"> 不会阻塞 HTML 解析,但会阻塞 JS 执行

12. 渲染树(Render Tree)

定义 渲染树是 DOM 树和 CSSOM 树结合后的产物,只包含需要在屏幕上显示的元素。

原理

Render Tree = DOM Tree + CSSOM Tree - display:none 的元素

渲染树不包含的元素

  • display: none 的元素
  • <head> 及其子元素
  • <script><meta> 等非视觉元素

渲染树包含的元素

  • visibility: hidden 的元素(占据空间,只是不可见)
  • opacity: 0 的元素

13. 布局(Layout / Reflow)

定义 布局是计算渲染树中每个节点在视口中的确切位置和尺寸的过程,也叫重排(Reflow)。

原理 浏览器从根节点开始递归计算每个节点的几何信息:

  • 位置(x, y 坐标)
  • 尺寸(width, height)
  • 边距(margin, padding, border)

会触发布局的操作

  • 添加/删除 DOM 元素
  • 修改元素尺寸(width, height, padding, margin, border)
  • 修改元素位置(top, left, right, bottom)
  • 修改字体大小
  • 窗口大小变化
  • 读取某些属性(offsetTop, offsetWidth, clientHeight, getComputedStyle 等)

14. 绘制(Paint)

定义 绘制是将渲染树中的每个节点转换为屏幕上的像素的过程,也叫重绘(Repaint)。

原理 绘制阶段会处理:

  • 文字渲染
  • 颜色填充
  • 边框绘制
  • 阴影效果
  • 渐变效果

绘制命令会被记录为 Display List

Paint Record:
  - Draw rect at (10, 10) size (100, 50) fill red
  - Draw text "Hello" at (15, 20)
  - Draw box-shadow ...

15. 合成(Composite)

定义 合成是将多个图层(Layer)按照正确的顺序合并为一个完整画面的过程。

原理

图层1: 背景色
图层2: 文字内容
图层3: 固定定位元素
图层4: 使用 transform 的元素
      ↓
    合成
      ↓
   最终画面

创建新合成层的条件

  • 使用 transformopacity 动画
  • 使用 will-change 属性
  • 使用 position: fixed/sticky
  • 使用 3D 变换
  • 使用 <video><canvas><iframe> 等元素

优势 合成操作在 GPU 上完成,不需要重新布局和绘制,性能极高。


16. 重排(Reflow / Layout)

定义 重排是指当 DOM 变化影响了元素的几何属性(尺寸、位置),浏览器需要重新计算元素布局的过程。

原理 重排的范围:

  • 局部重排:只影响部分元素
  • 全局重排:整个页面重新布局(如窗口大小变化)

触发重排的代码示例

// 每次修改都会触发重排
el.style.width = '100px';
el.style.height = '200px';
el.style.margin = '10px';

// 优化方案:一次性修改
el.style.cssText = 'width: 100px; height: 200px; margin: 10px;';

// 或使用 class
el.classList.add('new-style');

17. 重绘(Repaint)

定义 重绘是指当元素的外观发生变化(不影响布局),浏览器重新绘制该元素的过程。

触发重绘的操作

  • 修改 colorbackground-color
  • 修改 visibility
  • 修改 outline
  • 修改 box-shadow
  • 修改 border-radius
// 只触发重绘,不触发重排
el.style.color = 'red';
el.style.backgroundColor = 'blue';

18. 重排与重绘的区别

定义对比

  • 重排(Reflow):元素几何属性变化导致的布局重新计算
  • 重绘(Repaint):元素外观变化导致的像素重新绘制

对比表格

维度重排(Reflow)重绘(Repaint)
触发条件元素尺寸、位置、内容变化颜色、背景、边框等外观变化
性能开销较大(需要重新计算布局)较小(只需重新绘制像素)
影响范围可能影响父元素和兄弟元素只影响当前元素
是否一定触发重绘
举例修改 width、height、margin修改 color、background

关系

  • 重排一定会触发重绘
  • 重绘不一定触发重排

19. 减少重排重绘

实现步骤 + 代码示例

  1. 批量修改 DOM
// ❌ 不好的做法:每次修改都触发重排
el.style.width = '100px';
el.style.height = '200px';
el.style.margin = '10px';

// ✅ 好的做法:使用 cssText 一次性修改
el.style.cssText = 'width: 100px; height: 200px; margin: 10px;';

// ✅ 或使用 class
el.className += ' new-style';

// ✅ 或使用 CSS 自定义属性
el.style.setProperty('--width', '100px');
  1. 将元素脱离文档流
// 先将元素隐藏,修改后再显示
el.style.display = 'none';
el.style.width = '100px';
el.style.height = '200px';
el.style.display = 'block';
// 只触发 2 次重排
  1. 使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
// 只触发 1 次重排
  1. 读写分离
// ❌ 不好的做法:读写交替,强制同步布局
el.style.width = el.offsetWidth + 10 + 'px';
el.style.height = el.offsetHeight + 10 + 'px';

// ✅ 好的做法:先读后写
const w = el.offsetWidth;
const h = el.offsetHeight;
el.style.width = w + 10 + 'px';
el.style.height = h + 10 + 'px';
  1. 使用 will-change
.will-animate {
  will-change: transform; /* 提前告知浏览器,创建合成层 */
}

三、事件循环(Event Loop)

21. 事件循环(Event Loop)

定义 事件循环是 JavaScript 的执行模型,用于协调同步任务和异步任务的执行。

原理 JavaScript 是单线程语言,通过事件循环机制实现异步非阻塞执行。

graph TD
    A[执行同步代码] --> B{调用栈为空?}
    B -->|否| A
    B -->|是| C{微任务队列有任务?}
    C -->|是| D[执行所有微任务]
    D --> C
    C -->|否| E[执行一个宏任务]
    E --> C

执行规则

  1. 执行同步代码(调用栈)
  2. 调用栈为空后,执行所有微任务
  3. 微任务清空后,执行一个宏任务
  4. 重复步骤 2-3

代码示例

console.log('1'); // 同步

setTimeout(() => {
  console.log('2'); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('3'); // 微任务
});

console.log('4'); // 同步

// 输出顺序: 1 -> 4 -> 3 -> 2

常见误区

  • 误区:setTimeout(fn, 0) 会立即执行。实际上它需要等待当前宏任务执行完,且微任务队列为空后才会执行

22. 宏任务(Macro Task)

定义 宏任务是事件循环中的大粒度异步任务。

常见的宏任务

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • I/O 操作
  • requestAnimationFrame
  • UI 渲染
  • 用户交互事件(click、keydown 等)
  • MessageChannel

特点

  • 每次事件循环只执行一个宏任务
  • 宏任务执行完后会执行所有微任务

23. 微任务(Micro Task)

定义 微任务是事件循环中的小粒度异步任务,优先级高于宏任务。

常见的微任务

  • Promise.then/catch/finally
  • async/await(本质是 Promise)
  • MutationObserver
  • queueMicrotask()
  • process.nextTick(Node.js)

特点

  • 当前宏任务执行完后,会清空所有微任务
  • 微任务中添加的微任务也会被执行(可能导致无限循环)

代码示例

Promise.resolve().then(() => {
  console.log('微任务 1');
  Promise.resolve().then(() => {
    console.log('微任务 2(嵌套)');
  });
});

setTimeout(() => {
  console.log('宏任务');
}, 0);

// 输出: 微任务1 -> 微任务2(嵌套) -> 宏任务

24. 宏任务与微任务的执行顺序

执行顺序规则

  1. 执行调用栈中的同步代码
  2. 调用栈为空后,执行所有微任务(包括微任务中新增的微任务)
  3. 微任务队列为空后,执行一个宏任务
  4. 重复步骤 2-3

综合示例

console.log('start');

setTimeout(() => {
  console.log('setTimeout1');
  Promise.resolve().then(() => {
    console.log('promise in setTimeout1');
  });
}, 0);

setTimeout(() => {
  console.log('setTimeout2');
}, 0);

Promise.resolve().then(() => {
  console.log('promise1');
  Promise.resolve().then(() => {
    console.log('promise2');
  });
});

console.log('end');

// 输出顺序:
// start
// end
// promise1
// promise2
// setTimeout1
// promise in setTimeout1
// setTimeout2

25. requestAnimationFrame

定义 requestAnimationFrame(rAF)是浏览器提供的专门用于动画的 API,会在浏览器下一次重绘之前执行回调。

原理

function animate() {
  // 执行动画
  element.style.transform = `translateX(${x}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

优势

  • 与屏幕刷新率同步(通常 60fps,即约 16.67ms 执行一次)
  • 页面隐藏时自动暂停,节省性能
  • 浏览器会优化回调执行时机,保证流畅度

与 setTimeout 对比

维度requestAnimationFramesetTimeout
执行时机浏览器重绘前延迟指定时间后
频率与屏幕刷新率同步不保证准确
页面隐藏时自动暂停继续执行
适用场景动画定时任务

26. requestIdleCallback

定义 requestIdleCallback 允许在浏览器空闲时段执行低优先级任务,不会阻塞高优先级任务。

原理

requestIdleCallback((deadline) => {
  console.log(`剩余时间: ${deadline.timeRemaining()}ms`);
  console.log(`是否超时: ${deadline.didTimeout}`);

  // 在空闲时间执行任务
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    doWork();
  }
});

适用场景

  • 数据分析上报
  • 预加载资源
  • 非关键计算

注意

  • 兼容性有限(不支持 Safari)
  • 回调每秒最多执行 20 次
  • 不要执行 DOM 操作

四、垃圾回收

28. 垃圾回收(Garbage Collection)

定义 垃圾回收是自动管理内存的机制,用于回收不再使用的对象所占用的内存空间。

原理 JavaScript 使用自动垃圾回收机制,开发者无需手动释放内存。主要的垃圾回收算法:

  • 引用计数(已基本淘汰)
  • 标记清除(Mark-Sweep)
  • 标记整理(Mark-Compact)
  • 分代回收(Generational Collection)

29. 标记清除(Mark-Sweep)

定义 标记清除是现代垃圾回收器最常用的算法,分为标记和清除两个阶段。

原理

graph LR
    A[标记阶段: 从根对象出发] --> B[标记所有可达对象]
    B --> C[清除阶段: 回收未标记对象]
  1. 标记阶段:从根对象(Global、执行栈中的变量)出发,遍历所有可达对象并标记
  2. 清除阶段:清除未被标记的对象,回收其内存

代码示例

let obj = { name: 'test' }; // obj 指向对象,对象可达
obj = null;                 // 原对象不再可达,下次 GC 时回收

缺点

  • 产生内存碎片
  • 需要暂停 JavaScript 执行(STW - Stop The World)

30. 引用计数(Reference Counting)

定义 引用计数算法通过追踪每个值被引用的次数来决定是否回收。

原理

  • 当一个值被引用时,引用计数 +1
  • 当引用被解除时,引用计数 -1
  • 引用计数为 0 时,立即回收

循环引用问题

function problem() {
  let obj1 = {};
  let obj2 = {};
  obj1.ref = obj2;
  obj2.ref = obj1;
  // obj1 和 obj2 互相引用,引用计数都不为 0
  // 但函数结束后它们实际上已经不可达
}
problem();
// 在引用计数算法下,这两个对象永远不会被回收

现状 现代浏览器(IE9+)已不再使用纯引用计数,而是采用标记清除算法。


31. V8 垃圾回收

定义 V8 是 Chrome 和 Node.js 使用的 JavaScript 引擎,其垃圾回收机制将内存分为新生代和老生代。

原理

V8 内存布局
├── 新生代(Young Generation):约 32MB
│    ├── From 空间
│    └── To 空间
└── 老生代(Old Generation):约 1.4GB
  • 新生代:存放存活时间短的对象(临时变量)
  • 老生代:存放存活时间长的对象(全局变量、闭包引用)

32. 分代回收

定义 分代回收基于"弱分代假说"(大多数对象都是朝生夕灭的)将内存分为不同代,采用不同的回收策略。

新生代回收策略(Scavenge 算法)

graph LR
    A[From 空间满] --> B[触发 Scavenge 回收]
    B --> C[存活对象复制到 To 空间]
    C --> D[清空 From 空间]
    D --> E[From 和 To 互换角色]

老生代回收策略(标记-清除 + 标记-整理)

  • 对象从新生代晋升到老生代
  • 使用标记清除回收,产生碎片时使用标记整理

晋升条件

  • 对象经历过一次 Scavenge 回收
  • To 空间使用率超过 25%(避免大对象导致内存浪费)

33. 新生代 / 老生代 / Scavenge 算法

新生代

  • 容量小(约 32MB)
  • 使用 Scavenge 算法
  • 回收频率高,速度快

老生代

  • 容量大(约 1.4GB)
  • 使用标记-清除和标记-整理算法
  • 回收频率低,耗时长

Scavenge 算法流程

1. 检查 From 空间是否已满
2. 遍历 From 空间中的存活对象
3. 将存活对象复制到 To 空间
4. 清空 From 空间
5. 交换 FromTo 的角色

34. 标记整理算法(Mark-Compact)

定义 标记整理算法在标记清除的基础上增加了整理步骤,将存活对象向一端移动,消除内存碎片。

原理

标记清除后:
[存活][碎片][存活][碎片][存活][碎片]

标记整理后:
[存活][存活][存活][空闲区域]

适用场景

  • 老生代内存碎片过多时
  • 标记整理比标记清除更耗时,所以不会每次都执行

35. 增量标记(Incremental Marking)

定义 增量标记将标记阶段拆分为多个小步骤,与 JavaScript 执行交替进行,减少 STW 时间。

原理

传统标记: [JS暂停] [完整标记] [JS恢复]
增量标记: [JS暂停] [部分标记] [JS执行] [部分标记] [JS执行] ...

优势

  • 减少每次暂停的时间
  • 提升应用响应性

代价

  • 总标记时间可能增加
  • 实现更复杂

36. 内存泄漏

定义 内存泄漏是指程序中已不再使用的对象仍然被引用,导致无法被垃圾回收。

常见场景

  1. 意外的全局变量
function fn() {
  leakedVar = 'I am global now'; // 忘记用 var/let/const
}
  1. 未清除的定时器
setInterval(() => {
  // 引用了外部大对象
  console.log(largeObject);
}, 1000); // 忘记 clearInterval
  1. 闭包引用
function createClosure() {
  const largeData = new Array(1000000).fill('data');
  return function() {
    console.log('closure'); // 即使不使用 largeData,也会被引用
  };
}
  1. 未移除的 DOM 引用
const elements = [];
function addElement() {
  const el = document.createElement('div');
  elements.push(el);
  document.body.appendChild(el);
}
function removeElement() {
  document.body.removeChild(el);
  // 但 elements 数组中仍然引用,无法被 GC
}
  1. 未清除的事件监听
const btn = document.getElementById('btn');
btn.addEventListener('click', function handler() {
  // 忘记 removeEventListener
});

五、浏览器缓存

41. 浏览器缓存机制

定义 浏览器缓存是将请求过的资源存储在本地,下次请求时直接使用本地缓存,减少网络请求和加载时间。

原理

graph TD
    A[请求资源] --> B{强缓存有效?}
    B -->|是| C[使用缓存, 返回 200 from cache]
    B -->|否| D{协商缓存有效?}
    D -->|是| E[返回 304 Not Modified]
    D -->|否| F[请求服务器, 返回 200 OK]

缓存类型

  • 强缓存:缓存有效期内直接使用本地缓存,不向服务器发送请求
  • 协商缓存:缓存过期后向服务器验证,若未修改则返回 304

42. 强缓存

定义 强缓存指在缓存有效期内,浏览器直接使用本地缓存,不向服务器发送任何请求。

控制字段

  • Cache-Control(HTTP/1.1)
  • Expires(HTTP/1.0)

示例

# 请求响应头
Cache-Control: max-age=3600  # 缓存 1 小时
请求流程:
第一次请求: ClientServer → 返回资源 + Cache-Control: max-age=3600
第二次请求(1小时内): Client → 直接使用缓存, 不发送请求 (200 from disk/memory cache)
第三次请求(1小时后): Client → 进入协商缓存流程

43. 协商缓存

定义 协商缓存指当强缓存失效后,浏览器向服务器发送请求,携带验证标识,由服务器判断资源是否发生变化。

控制字段

  • ETag / If-None-Match(精确匹配)
  • Last-Modified / If-Modified-Since(时间戳匹配)

示例

# 第一次请求 - 服务器响应
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

# 第二次请求 - 浏览器发送
If-None-Match: "abc123"
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT

# 服务器响应
304 Not Modified  # 资源未修改,使用本地缓存

44. Cache-Control

定义 Cache-Control 是 HTTP/1.1 引入的通用首部字段,用于控制缓存策略。

常用指令

指令说明
max-age=<seconds>缓存有效时间(秒)
no-cache需要与服务器验证后才能使用缓存
no-store不缓存任何内容
public响应可以被任何缓存存储
private响应只能被浏览器缓存
immutable资源在有效期内不会改变

示例

Cache-Control: no-cache, no-store, must-revalidate
Cache-Control: max-age=31536000, immutable  # 一年缓存,常用于带 hash 的文件名

Cache-Control vs Expires

Cache-Control: max-age=3600      # 相对时间,从请求响应时计算
Expires: Wed, 21 Oct 2023 08:28  # 绝对时间,依赖客户端时钟

45. ETag

定义 ETag(Entity Tag)是服务器为资源生成的唯一标识符,用于协商缓存。

原理

  • 服务器根据文件内容生成 hash 值作为 ETag
  • 浏览器下次请求时携带 If-None-Match
  • 服务器比对 ETag,一致则返回 304

优势

  • 比 Last-Modified 更精确(内容没变即使时间变了也不会重新下载)
  • 可以精确到秒以下

生成方式

# Nginx 配置
etag on;

46. Last-Modified

定义 Last-Modified 是资源在服务器上的最后修改时间。

原理

  • 服务器返回 Last-Modified
  • 浏览器下次请求时携带 If-Modified-Since
  • 服务器比对时间,未修改则返回 304

局限性

  • 只能精确到秒
  • 文件内容未变但修改时间变了会导致重新下载
  • 某些服务器无法提供精确的修改时间

47. 缓存位置

定义 浏览器缓存按存储位置分为多个层级,按优先级从高到低排列。

缓存层级

Service Worker Cache (可编程控制)
       ↓
Memory Cache (内存缓存,快速但短暂)
       ↓
Disk Cache (磁盘缓存,持久化)
       ↓
Push Cache (HTTP/2 推送,短暂)

特点对比

缓存类型存储位置生命周期适用场景
Service Worker磁盘持久化离线缓存
Memory Cache内存会话级别快速访问
Disk Cache磁盘持久化大部分资源
Push Cache内存会话级别HTTP/2 推送

48. Service Worker Cache

定义 Service Worker 是一个运行在后台的独立线程,可以拦截网络请求并返回缓存资源。

原理

// 安装阶段:缓存资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/',
        '/index.html',
        '/main.css',
        '/app.js',
      ]);
    })
  );
});

// 拦截请求
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

缓存策略

  • Cache First(缓存优先)
  • Network First(网络优先)
  • Stale-While-Revalidate(缓存同时更新)

49. Memory Cache vs Disk Cache

Memory Cache

  • 存储在内存中,读写速度极快
  • 生命周期与标签页一致,关闭标签页即清除
  • 通常缓存体积较小的资源(JS、CSS)

Disk Cache

  • 存储在磁盘中,读写速度较慢
  • 持久化,关闭浏览器仍然存在
  • 缓存体积较大的资源(图片、字体、音视频)
// 可以通过 Performance API 查看资源缓存来源
performance.getEntriesByType('resource').forEach(entry => {
  console.log(`${entry.name}: ${entry.transferSize === 0 ? 'from cache' : 'from network'}`);
});

50. 缓存策略

定义 缓存策略是根据资源类型和业务需求制定的缓存规则。

最佳实践

静态资源(带 hash 文件名):
  Cache-Control: max-age=31536000, immutable
  # 一年缓存,文件名变化自动更新

HTML 页面:
  Cache-Control: no-cache
  # 每次验证,保证获取最新页面

API 请求:
  Cache-Control: no-store
  # 不缓存,保证数据实时

不常变的静态资源(如 CDN 资源):
  Cache-Control: public, max-age=86400
  # 一天缓存

Webpack 配置示例

// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash].js',  // 内容变化才更新文件名
    chunkFilename: '[name].[contenthash].js',
  },
};

六、浏览器存储

54. 浏览器存储

定义 浏览器存储是指在客户端保存数据的机制,用于在页面之间或会话之间持久化数据。

存储类型对比

特性CookielocalStoragesessionStorageIndexedDB
容量~4KB~5-10MB~5-10MB无上限(建议 >50MB)
有效期可设置过期时间永久会话级别永久
同步/异步同步同步同步异步
与服务端通信自动携带不携带不携带不携带
API字符串操作简单键值对简单键值对事务型数据库

55. Cookie

定义 Cookie 是存储在客户端的小段数据,每次请求会自动携带到服务端。

原理

// 设置 Cookie
document.cookie = 'name=value; path=/; max-age=3600; secure; samesite=strict';

// 读取 Cookie
console.log(document.cookie); // "name=value; other=value2"

// 删除 Cookie(设置过期时间为过去)
document.cookie = 'name=; max-age=0; path=/';

属性

  • path:Cookie 作用路径
  • domain:Cookie 作用域名
  • max-age / expires:有效期
  • secure:仅 HTTPS 传输
  • httpOnly:禁止 JS 访问
  • sameSite:跨站请求策略(Strict / Lax / None)

常见误区

  • 误区:Cookie 可以存储大量数据。实际上每个 Cookie 最大 4KB,每个域名最多 20-50 个

56. localStorage

定义 localStorage 是 Web Storage API 的一部分,提供持久化的键值对存储。

原理

// 存储
localStorage.setItem('user', JSON.stringify({ name: 'Alice', age: 25 }));

// 读取
const user = JSON.parse(localStorage.getItem('user'));

// 删除
localStorage.removeItem('user');

// 清空
localStorage.clear();

// 监听变化
window.addEventListener('storage', (e) => {
  console.log(`Key changed: ${e.key}, Old: ${e.oldValue}, New: ${e.newValue}`);
});

特性

  • 同源策略限制(协议 + 域名 + 端口相同)
  • 数据永久存储,除非手动删除
  • 同步 API,大量读写可能阻塞主线程
  • 只能存储字符串

57. sessionStorage

定义 sessionStoragelocalStorage API 相同,但数据仅在当前会话有效。

原理

sessionStorage.setItem('temp', 'data');
console.log(sessionStorage.getItem('temp')); // "data"

// 关闭标签页或浏览器后,数据自动清除

与 localStorage 的区别

  • sessionStorage 在标签页关闭后清除
  • localStorage 在标签页关闭后仍然保留
  • 每个标签页的 sessionStorage 是独立的

58. IndexedDB

定义 IndexedDB 是一个运行在浏览器中的非关系型数据库,支持大量结构化数据的存储。

原理

// 打开数据库
const request = indexedDB.open('myDB', 1);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // 创建对象仓库(类似表)
  const store = db.createObjectStore('users', { keyPath: 'id' });
  store.createIndex('name', 'name', { unique: false });
};

request.onsuccess = (event) => {
  const db = event.target.result;
  const tx = db.transaction('users', 'readwrite');
  const store = tx.objectStore('users');

  // 添加数据
  store.add({ id: 1, name: 'Alice', age: 25 });

  // 查询数据
  const getRequest = store.get(1);
  getRequest.onsuccess = () => {
    console.log(getRequest.result);
  };
};

特性

  • 异步 API,不阻塞主线程
  • 支持事务
  • 支持索引查询
  • 容量大(通常 > 50MB)
  • 支持存储二进制数据

59. Web SQL

定义 Web SQL 是一个基于 SQLite 的浏览器数据库 API,已被 W3C 废弃。

原理

// 已废弃,不推荐使用
const db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
db.transaction((tx) => {
  tx.executeSql('CREATE TABLE IF NOT EXISTS users (id, name)');
  tx.executeSql('INSERT INTO users VALUES (1, "Alice")');
});

现状

  • 已被 W3C 废弃
  • 替代方案:IndexedDB
  • 不建议在新项目中使用

60. 存储限制

各存储方式的限制

存储方式限制
Cookie每个 4KB,每个域名约 20-50 个
localStorage约 5-10MB(各浏览器不同)
sessionStorage约 5-10MB
IndexedDB可用磁盘空间的 1/3 到 1/2
Cache API约 50MB - 数 GB

检查存储配额

if (navigator.storage && navigator.storage.estimate) {
  const estimate = await navigator.storage.estimate();
  console.log(`已使用: ${estimate.usage} bytes`);
  console.log(`配额: ${estimate.quota} bytes`);
}

七、跨域与安全

62. 同源策略(Same-Origin Policy)

定义 同源策略是浏览器最核心的安全机制,限制不同源之间的交互。

原理 同源的定义:协议 + 域名 + 端口完全相同。

URL是否同源原因
http://example.com/page1✅ 同源相同
https://example.com/page2❌ 不同源协议不同
http://api.example.com/page❌ 不同源域名不同
http://example.com:8080/page❌ 不同源端口不同

同源策略限制

  • Cookie、localStorage、IndexedDB 无法跨域读取
  • DOM 无法跨域获取
  • AJAX 请求无法跨域(需要 CORS)

63. 跨域

定义 跨域是指当前页面的源与请求目标的源不同,被浏览器的同源策略限制。

产生的原因

  • 浏览器的同源策略限制
  • 前端和服务端部署在不同域名
  • 微前端架构中不同子应用需要通信

跨域只存在于浏览器端

  • 服务端请求不存在跨域问题
  • <script><img><link> 标签天然支持跨域

64. CORS(跨域资源共享)

定义 CORS(Cross-Origin Resource Sharing)是 W3C 标准,允许服务器明确指定哪些源可以访问其资源。

原理

graph LR
    A[浏览器发起跨域请求] --> B{是否简单请求?}
    B -->|是| C[直接发送, 携带 Origin 头]
    B -->|否| D[发送 OPTIONS 预检请求]
    D --> E[服务器返回 CORS 头]
    E --> F{预检通过?}
    F -->|是| G[发送实际请求]
    F -->|否| H[浏览器拦截, 报错]

简单请求的条件

  • 请求方法:GET / HEAD / POST
  • Content-Type:text/plain / multipart/form-data / application/x-www-form-urlencoded
  • 无自定义请求头

服务器响应头

Access-Control-Allow-Origin: http://example.com  # 允许的源 (* 表示所有)
Access-Control-Allow-Methods: GET, POST, PUT     # 允许的方法
Access-Control-Allow-Headers: Content-Type       # 允许的请求头
Access-Control-Allow-Credentials: true           # 允许携带 Cookie
Access-Control-Max-Age: 86400                    # 预检请求缓存时间

代码示例

// 前端
fetch('https://api.example.com/data', {
  method: 'POST',
  credentials: 'include', // 携带 Cookie
  headers: { 'Content-Type': 'application/json' },
});

// 后端 (Node.js / Express)
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://example.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  res.header('Access-Control-Allow-Credentials', 'true');
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  next();
});

65. JSONP

定义 JSONP(JSON with Padding)利用 <script> 标签不受同源策略限制的特性实现跨域。

原理

// 前端
function handleResponse(data) {
  console.log('收到数据:', data);
}

const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.head.appendChild(script);
// 后端响应
// 返回: handleResponse({"name": "Alice", "age": 25})

局限性

  • 只支持 GET 请求
  • 存在 XSS 风险
  • 无法获取 HTTP 状态码
  • 现代项目推荐使用 CORS

66. 代理跨域

定义 代理跨域通过在同源服务器上搭建代理,将请求转发到目标服务器,绕开浏览器的跨域限制。

开发环境配置

// Vite 配置
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
};

// Webpack 配置
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
      },
    },
  },
};

Nginx 代理配置

server {
  listen 80;
  server_name example.com;

  location /api/ {
    proxy_pass https://api.example.com/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

优势

  • 对前端透明,无需修改请求代码
  • 支持所有请求方法和数据类型
  • 可以添加额外的安全控制

67. XSS(跨站脚本攻击)

定义 XSS(Cross-Site Scripting)是攻击者向页面注入恶意脚本,在其他用户的浏览器中执行。

攻击类型

类型说明示例
反射型恶意脚本在 URL 参数中?q=<script>alert('xss')</script>
存储型恶意脚本存储在服务器(数据库)评论中注入脚本
DOM 型客户端脚本直接操作 DOM 导致innerHTML = location.hash

防御措施

  1. HTML 转义
function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}
  1. CSP(Content Security Policy)
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com
  1. 使用安全的 DOM API
// ❌ 危险
el.innerHTML = userInput;

// ✅ 安全
el.textContent = userInput;
  1. 设置 Cookie 的 httpOnly
Set-Cookie: session=abc123; httpOnly; secure; samesite=strict

68. CSRF(跨站请求伪造)

定义 CSRF(Cross-Site Request Forgery)是攻击者诱导已登录用户在其浏览器中执行非预期的操作。

攻击流程

graph LR
    A[用户登录银行网站] --> B[获得 Cookie]
    B --> C[用户访问恶意网站]
    C --> D[恶意网站发起银行转账请求]
    D --> E[浏览器自动携带 Cookie]
    E --> F[银行服务器处理请求]
    F --> G[转账成功(被攻击)]

防御措施

  1. CSRF Token
<!-- 表单中携带 CSRF Token -->
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value="random_token_value">
  <input type="text" name="amount">
  <button type="submit">转账</button>
</form>
  1. SameSite Cookie
Set-Cookie: session=abc123; samesite=strict
  1. 验证 Referer / Origin 头
// 后端验证
const origin = req.headers.origin;
if (origin !== 'https://mybank.com') {
  return res.status(403).send('Forbidden');
}
  1. 自定义请求头
// AJAX 请求携带自定义头,浏览器会触发预检
fetch('/api/transfer', {
  headers: { 'X-Requested-With': 'XMLHttpRequest' },
});

69. 点击劫持(Clickjacking)

定义 点击劫持是将目标网页嵌入到透明 iframe 中,诱导用户点击的操作。

原理

<!-- 攻击者页面 -->
<iframe src="https://bank.com/transfer" style="opacity: 0; position: absolute;"></iframe>
<button style="position: absolute; top: 100px; left: 100px;">
  点击领取奖品
</button>
<!-- 用户以为点击按钮,实际点击了 iframe 中的转账按钮 -->

防御措施

# X-Frame-Options
X-Frame-Options: DENY          # 禁止嵌入
X-Frame-Options: SAMEORIGIN    # 仅同源可嵌入

# CSP (推荐)
Content-Security-Policy: frame-ancestors 'self'

70. 安全策略总结

综合防御清单

攻击类型防御手段
XSSHTML 转义、CSP、httpOnly Cookie、使用 textContent
CSRFCSRF Token、SameSite Cookie、验证 Referer
点击劫持X-Frame-Options、CSP frame-ancestors
中间人攻击HTTPS、HSTS
恶意资源SRI(Subresource Integrity)

SRI 示例

<script
  src="https://cdn.example.com/jquery.min.js"
  integrity="sha384-xxx..."
  crossorigin="anonymous"
></script>

八、性能优化

71. 浏览器性能优化概述

定义 浏览器性能优化是指通过各种技术手段提升页面加载速度和运行效率,改善用户体验。

优化维度

  • 加载性能:减少资源大小、减少请求数、利用缓存
  • 渲染性能:减少重排重绘、使用合成层、虚拟化列表
  • 运行性能:优化 JS 执行、使用 Web Worker、减少内存占用
  • 感知性能:骨架屏、预加载、懒加载

72. 首屏优化

定义 首屏优化是指优化用户第一次看到的页面内容的加载速度。

优化策略

  1. 减少关键资源数量
<!-- 内联关键 CSS -->
<style>
  /* 首屏必需的 CSS */
  .hero { height: 100vh; }
</style>

<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="non-critical.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">
  1. 资源压缩
// Gzip / Brotli 压缩
// webpack 配置
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
  plugins: [
    new CompressionPlugin({
      algorithm: 'brotliCompress',
      test: /\.(js|css|html|svg)$/,
    }),
  ],
};
  1. 代码分割
// 路由懒加载
const Home = () => import('./views/Home.vue');
const About = () => import('./views/About.vue');

// 动态导入
button.addEventListener('click', async () => {
  const { default: heavyModule } = await import('./heavy-module.js');
  heavyModule.init();
});
  1. CDN 加速
  • 将静态资源部署到 CDN
  • 就近节点分发,减少网络延迟

73. 白屏优化

定义 白屏时间是指从用户访问页面到看到第一个像素的时间。

优化策略

  1. 将 JS 放在底部或使用 defer/async
<!-- 阻塞解析 -->
<script src="app.js"></script>

<!-- 不阻塞解析,DOMContentLoaded 前执行 -->
<script src="app.js" defer></script>

<!-- 不阻塞解析,加载完立即执行 -->
<script src="app.js" async></script>
  1. SSR(服务端渲染)
// Next.js 示例
export async function getServerSideProps() {
  return { props: { data: await fetchData() } };
}
  1. 预渲染(Prerendering)
// prerender-spa-plugin 在构建时生成静态 HTML

74. 懒加载(Lazy Loading)

定义 懒加载是指延迟加载非关键资源,直到需要时才加载。

实现方式

  1. 图片懒加载
<!-- 原生懒加载 -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="图片">

<!-- IntersectionObserver 实现 -->
<script>
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
</script>
  1. 组件懒加载
// React
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

// Vue
const LazyComponent = () => import('./HeavyComponent.vue');

75. 预加载(Preloading)

定义 预加载是提前加载将来可能需要的资源。

实现方式

<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//api.example.com">

<!-- 预连接 -->
<link rel="preconnect" href="https://api.example.com">

<!-- 预获取 -->
<link rel="prefetch" href="/next-page.js">

<!-- 预加载(高优先级) -->
<link rel="preload" href="/font.woff2" as="font" crossorigin>

<!-- 模块预加载 -->
<link rel="modulepreload" href="/app.js">

区别

方式优先级适用场景
dns-prefetch跨域域名解析
preconnect建立连接
prefetch下一个导航可能需要的资源
preload当前页面需要的资源

76. 预渲染(Prerendering)

定义 预渲染是在构建时将页面生成为静态 HTML,用户访问时直接展示。

原理

构建时: Headless Chrome → 渲染页面 → 生成静态 HTML
用户访问: 直接返回预渲染的 HTML → 快速展示

工具

  • prerender-spa-plugin(Webpack)
  • vite-plugin-prerender
  • Nuxt.js / Next.js 的静态生成模式

77. 骨架屏(Skeleton Screen)

定义 骨架屏是页面加载时展示的占位布局,模拟最终页面的结构,提升感知性能。

实现方式

  1. CSS 实现
.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}

@keyframes loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
  1. HTML 结构
<div class="skeleton-card">
  <div class="skeleton skeleton-title"></div>
  <div class="skeleton skeleton-text"></div>
  <div class="skeleton skeleton-text"></div>
</div>

78. 性能监控

定义 性能监控是通过收集页面性能指标,持续分析和优化页面性能。

监控方式

  1. Performance API
// 获取页面性能数据
const perf = performance.getEntriesByType('navigation')[0];
console.log('DNS 查询时间:', perf.domainLookupEnd - perf.domainLookupStart);
console.log('TCP 连接时间:', perf.connectEnd - perf.connectStart);
console.log('DOM 解析时间:', perf.domContentLoadedEventEnd - perf.navigationStart);
console.log('页面完全加载:', perf.loadEventEnd - perf.navigationStart);
  1. 自定义指标上报
// 使用 sendBeacon 上报(页面卸载时也能发送)
function reportMetric(name, value) {
  navigator.sendBeacon('/api/metrics', JSON.stringify({ name, value }));
}

// 监听 FCP
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  reportMetric('FCP', entries[0].startTime);
}).observe({ type: 'paint', buffered: true });

79. Performance API

定义 Performance API 是浏览器提供的性能测量接口,可以获取页面加载各阶段的时间。

关键指标获取

// 导航计时
const [nav] = performance.getEntriesByType('navigation');
console.log({
  'DNS 查询': nav.domainLookupEnd - nav.domainLookupStart,
  'TCP 连接': nav.connectEnd - nav.connectStart,
  'TTFB': nav.responseStart - nav.requestStart,
  'DOM 解析': nav.domContentLoadedEventEnd - nav.domInteractive,
  '资源加载': nav.loadEventEnd - nav.domContentLoadedEventEnd,
});

// 资源计时
const resources = performance.getEntriesByType('resource');
resources.forEach(r => {
  console.log(`${r.name}: ${(r.duration).toFixed(2)}ms`);
});

80. Lighthouse

定义 Lighthouse 是 Google 提供的开源自动化工具,用于审计页面性能、可访问性、SEO 等。

使用方式

# CLI 使用
npx lighthouse https://example.com --view --output=html

# Node API
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

async function audit() {
  const chrome = await chromeLauncher.launch();
  const result = await lighthouse('https://example.com', { port: chrome.port });
  console.log(result.lhr.categories.performance.score);
  await chrome.kill();
}

核心指标

  • Performance(性能)
  • Accessibility(可访问性)
  • Best Practices(最佳实践)
  • SEO(搜索引擎优化)
  • PWA(渐进式 Web 应用)

81. Web Vitals

定义 Web Vitals 是 Google 提出的一组衡量页面用户体验的核心指标。

核心指标

指标全称含义优秀阈值
LCPLargest Contentful Paint最大内容绘制时间≤ 2.5s
FIDFirst Input Delay首次输入延迟≤ 100ms
CLSCumulative Layout Shift累积布局偏移≤ 0.1
INPInteraction to Next Paint交互到下一次绘制≤ 200ms

测量方法

import { onLCP, onFID, onCLS } from 'web-vitals';

onLCP(console.log);
onFID(console.log);
onCLS(console.log);

82. FCP(First Contentful Paint)

定义 FCP 是浏览器渲染第一个文本、图像、SVG 或非白色 Canvas 元素的时间。

测量

new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach(entry => {
    if (entry.name === 'first-contentful-paint') {
      console.log('FCP:', entry.startTime);
    }
  });
}).observe({ type: 'paint', buffered: true });

优化方法

  • 内联关键 CSS
  • 延迟加载非关键 JS
  • 使用服务端渲染
  • 优化首屏资源大小

83. LCP(Largest Contentful Paint)

定义 LCP 是视口内最大的文本或图片元素完成渲染的时间。

测量

import { onLCP } from 'web-vitals';
onLCP(({ value }) => console.log('LCP:', value));

优化方法

  • 预加载 LCP 元素:<link rel="preload" as="image" href="hero.jpg">
  • 优化图片格式(WebP、AVIF)
  • 使用 CDN
  • 减少首屏资源数量
  • 服务器端渲染

84. FID(First Input Delay)

定义 FID 是从用户首次与页面交互(点击、触摸、按键)到浏览器响应的时间。

注意:FID 已被 INP(Interaction to Next Paint)取代。

优化方法

  • 减少/延迟 JavaScript 执行
  • 使用 Web Worker 处理计算密集型任务
  • 减少主线程工作
  • 代码分割

85. CLS(Cumulative Layout Shift)

定义 CLS 是衡量页面在加载过程中布局偏移的累积程度。

测量

import { onCLS } from 'web-vitals';
onCLS(({ value }) => console.log('CLS:', value));

导致 CLS 的原因

  • 没有指定尺寸的图片/视频
  • 动态插入的内容(广告、弹窗)
  • 字体切换导致的布局变化

优化方法

<!-- 指定图片尺寸 -->
<img src="hero.jpg" width="800" height="600" alt="Hero">

<!-- 使用 aspect-ratio -->
<style>
  .media { aspect-ratio: 16 / 9; }
</style>

<!-- 预留广告位空间 -->
<div class="ad-slot" style="min-height: 250px;"></div>

86. TTI(Time to Interactive)

定义 TTI 是页面完全可交互的时间点,即页面已显示主要视觉内容,且能可靠响应用户交互。

测量

import { onTTFI, onFCP } from 'web-vitals';
// TTI 需要结合 Long Tasks 分析

计算方式 从 FCP 开始,找到连续 5 秒内:

  • 没有长任务(>50ms)
  • 没有超过 2 个待处理的 GET 请求

优化方法

  • 代码分割和懒加载
  • 优化关键渲染路径
  • 减少主线程阻塞
  • 使用 Web Worker

九、浏览器兼容

87. 浏览器兼容

定义 浏览器兼容是指确保网页在不同浏览器、不同版本中都能正常显示和交互。

兼容性处理策略

  • 特性检测(推荐)
  • Polyfill
  • CSS Hack(不推荐)
  • 渐进增强
  • 优雅降级

88. 浏览器内核

定义 浏览器内核是浏览器的核心组件,负责解析 HTML、CSS、渲染页面和执行 JavaScript。

内核组成

  • 渲染引擎(排版引擎):负责解析 HTML、CSS,渲染页面
  • JS 引擎:负责解析和执行 JavaScript
浏览器渲染引擎JS 引擎
ChromeBlinkV8
FirefoxGeckoSpiderMonkey
SafariWebKitJavaScriptCore
Edge (新版)BlinkV8
Edge (旧版)EdgeHTMLChakra
IETridentChakra (旧版)

89. Trident

定义 Trident(又称 MSHTML)是微软 IE 浏览器的渲染引擎。

特点

  • 仅用于 IE 浏览器
  • 已停止开发,被 EdgeHTML / Blink 替代
  • 存在大量兼容性问题
  • 已知的 hasLayout 问题

版本对应

IE 版本Trident 版本
IE 6Trident 4.0
IE 7Trident 5.0
IE 8Trident 4.0
IE 9Trident 5.0
IE 10Trident 6.0
IE 11Trident 7.0

90. Gecko

定义 Gecko 是 Mozilla 开发的开源渲染引擎,用于 Firefox 浏览器。

特点

  • 开源,遵循 W3C 标准
  • 支持 WebExtensions API
  • JS 引擎为 SpiderMonkey

User-Agent 示例

Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0

91. WebKit

定义 WebKit 是一个开源的浏览器引擎,最初由 Apple 从 KHTML 分支而来。

使用范围

  • Safari(macOS / iOS)
  • 旧版 Chrome(2013 年前)
  • 旧版 Android 浏览器

JS 引擎

  • JavaScriptCore(Nitro)

92. Blink

定义 Blink 是 Google 从 WebKit 分支而来的渲染引擎,用于 Chrome 和 Edge。

特点

  • 2013 年从 WebKit 分支
  • 多进程架构
  • 更快的 CSS/JS 执行
  • 更积极地推进 Web 标准

市场份额

  • Chrome(全球约 65%+)
  • Edge(基于 Chromium)
  • Opera
  • 大部分国产浏览器

93. 兼容性处理

策略

  1. 特性检测(推荐)
// 检测浏览器是否支持某个特性
if ('geolocation' in navigator) {
  navigator.geolocation.getCurrentPosition(successCallback);
} else {
  // 降级处理
  useFallbackGeolocation();
}

// Modernizr 库
if (Modernizr.flexbox) {
  // 使用 Flexbox
}
  1. Polyfill
<!-- 按需加载 Polyfill -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=Promise,Array.from"></script>
  1. Autoprefixer
// PostCSS 配置 - 自动添加浏览器前缀
module.exports = {
  plugins: [
    require('autoprefixer')({
      overrideBrowserslist: ['> 1%', 'last 2 versions', 'not dead'],
    }),
  ],
};

94. Polyfill

定义 Polyfill 是一段代码(通常是 JS),用于在不支持某些现代 API 的老浏览器中模拟这些 API。

示例

// Promise Polyfill (简化版)
if (typeof Promise === 'undefined') {
  window.Promise = function(executor) {
    // 简化实现
    const callbacks = [];
    this.then = function(onFulfilled) {
      callbacks.push(onFulfilled);
      return this;
    };
    executor(
      (value) => callbacks.forEach(fn => fn(value)),
      (error) => { /* handle error */ }
    );
  };
}

常见 Polyfill 库

  • core-js:ES6+ API 的 Polyfill
  • babel-polyfill(已被 @babel/preset-env + core-js 替代)
  • polyfill.io:按需加载的在线 Polyfill 服务

95. CSS Hack

定义 CSS Hack 是利用不同浏览器对 CSS 解析的差异,针对特定浏览器编写样式的技巧。

常见 Hack

/* IE6/7 识别 * 前缀 */
.box {
  width: 100px;
  *width: 120px; /* IE6/7 */
  _width: 140px; /* IE6 only */
}

/* IE10+ 媒体查询 Hack */
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
  .box { width: 110px; } /* IE10/11 */
}

/* Safari/Chrome (Webkit) Hack */
@media screen and (-webkit-min-device-pixel-ratio: 0) {
  .box { width: 105px; }
}

/* Firefox Hack */
@-moz-document url-prefix() {
  .box { width: 108px; }
}

注意

  • CSS Hack 不利于维护
  • 推荐使用特性检测 + 渐进增强

96. 条件注释

定义 条件注释是 IE 浏览器特有的功能,用于针对特定 IE 版本加载不同的代码。

语法

<!-- IE6 -->
<!--[if IE 6]>
  <script src="ie6-fix.js"></script>
<![endif]-->

<!-- IE7 及以下 -->
<!--[if lte IE 7]>
  <link rel="stylesheet" href="ie7.css">
<![endif]-->

<!-- 非 IE -->
<!--[if !IE]>-->
  <script src="standard.js"></script>
<!--<![endif]-->

现状

  • 仅 IE 支持,IE10+ 已废弃
  • 现代浏览器将其视为普通 HTML 注释
  • 已不推荐使用

97. 特性检测

定义 特性检测是通过检测浏览器是否支持某个 API 或 CSS 属性,来决定使用哪种实现方式。

实现方式

// JS 特性检测
function supportsWebP() {
  const canvas = document.createElement('canvas');
  return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
}

// CSS 特性检测 (@supports)
@supports (display: grid) {
  .container { display: grid; }
}

@supports not (display: grid) {
  .container { display: flex; }
}

// 使用 Modernizr
if (Modernizr.cssgrid) {
  // 使用 Grid
}

与浏览器检测的对比

方式做法推荐度
特性检测检查是否支持某功能✅ 推荐
浏览器检测检查 User-Agent❌ 不推荐

98. User-Agent

定义 User-Agent(UA)是浏览器在 HTTP 请求头中发送的标识字符串,包含浏览器、操作系统等信息。

示例

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36

解析示例

function parseUA(ua) {
  const isChrome = /Chrome/.test(ua) && !/Edge/.test(ua);
  const isFirefox = /Firefox/.test(ua);
  const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua);
  const isEdge = /Edg/.test(ua);
  const isIE = /MSIE|Trident/.test(ua);
  const isMobile = /Mobi|Android|iPhone/.test(ua);
  return { isChrome, isFirefox, isSafari, isEdge, isIE, isMobile };
}

console.log(parseUA(navigator.userAgent));

十、其他浏览器特性

99-100. History API

定义 History API 允许在不刷新页面的情况下操作浏览器历史记录。

核心方法

// 添加历史记录
history.pushState({ page: 1 }, 'title 1', '/page1');

// 替换当前历史记录
history.replaceState({ page: 2 }, 'title 2', '/page2');

// 监听浏览器前进后退
window.addEventListener('popstate', (event) => {
  console.log('当前状态:', event.state);
  console.log('当前路径:', location.pathname);
});

// 导航
history.back();      // 后退
history.forward();   // 前进
history.go(-1);      // 后退一步

pushState vs replaceState

方法行为适用场景
pushState添加新历史记录跳转到新页面
replaceState替换当前历史记录更新当前页面状态(如筛选条件)

SPA 路由原理

// 前端路由的核心就是利用 History API
function navigateTo(path) {
  history.pushState(null, '', path);
  renderPage(path); // 渲染对应组件
}

window.addEventListener('popstate', () => {
  renderPage(location.pathname);
});

101. popstate 事件

定义 popstate 事件在用户点击浏览器前进/后退按钮或调用 history.back()/forward()/go() 时触发。

注意

  • pushStatereplaceState 不会触发 popstate
  • 初始加载不会触发 popstate
window.addEventListener('popstate', (event) => {
  console.log('State:', event.state);
  console.log('URL:', location.href);
  // 渲染对应页面
  renderPage(location.pathname);
});

102. Service Worker

定义 Service Worker 是运行在浏览器后台的独立线程,可以拦截网络请求、管理缓存、推送通知。

生命周期

安装(Installing) → 已安装(Installed/Waiting) → 激活(Activating) → 已激活(Activated) → 终止(Terminated)

注册

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(registration => {
    console.log('Service Worker 注册成功:', registration.scope);
  }).catch(err => {
    console.log('注册失败:', err);
  });
}

核心功能

  • 离线缓存
  • 推送通知
  • 后台同步
  • 拦截网络请求

103. PWA(Progressive Web App)

定义 PWA 是一种利用现代 Web API 将网页转变为类似原生应用体验的技术集合。

核心特性

  • Service Worker:离线支持
  • Web App Manifest:添加到主屏幕
  • HTTPS:安全要求
  • Push Notification:推送通知
  • Background Sync:后台同步

manifest.json

{
  "name": "My PWA App",
  "short_name": "PWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}

104. Web Workers

定义 Web Workers 允许在后台线程中运行 JavaScript,不阻塞主线程。

使用方式

// main.js - 主线程
const worker = new Worker('worker.js');

worker.postMessage({ data: 'hello' });

worker.onmessage = (e) => {
  console.log('收到:', e.data);
};

// worker.js - 工作线程
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage({ result });
};

限制

  • 无法访问 DOM
  • 无法访问 windowdocument
  • 可以访问 navigatorlocation(只读)、fetchIndexedDB
  • 数据通过结构化克隆算法传递

105. WebAssembly

定义 WebAssembly(Wasm)是一种可以在浏览器中运行的低级字节码格式,性能接近原生。

特点

  • 由 C / C++ / Rust 等语言编译而来
  • 性能接近原生
  • 与 JavaScript 互操作

使用示例

// 加载并运行 Wasm 模块
WebAssembly.instantiateStreaming(fetch('module.wasm'))
  .then(results => {
    const instance = results.instance;
    const result = instance.exports.add(3, 4);
    console.log(result); // 7
  });

106. WebSocket

定义 WebSocket 是 HTML5 提供的在单个 TCP 连接上进行全双工通信的协议。

特点

  • 持久连接
  • 全双工通信(客户端和服务器可以同时发送数据)
  • 低延迟
  • 跨域支持(需要服务器配合)

使用

const ws = new WebSocket('ws://example.com/socket');

ws.onopen = () => {
  ws.send('Hello Server!');
};

ws.onmessage = (event) => {
  console.log('收到:', event.data);
};

ws.onclose = () => {
  console.log('连接已关闭');
};

ws.onerror = (error) => {
  console.error('错误:', error);
};

107. Server-Sent Events(SSE)

定义 SSE 是服务器向客户端推送数据的单向通信协议。

特点

  • 单向通信(服务器 → 客户端)
  • 基于 HTTP,无需特殊协议
  • 自动重连
  • 支持事件 ID

使用

const eventSource = new EventSource('/stream');

eventSource.onmessage = (event) => {
  console.log('收到消息:', event.data);
};

eventSource.addEventListener('custom-event', (event) => {
  console.log('自定义事件:', event.data);
});

eventSource.onerror = () => {
  console.log('连接错误,自动重连中...');
};

// 关闭连接
eventSource.close();

WebSocket vs SSE

特性WebSocketSSE
通信方向双向单向(服务器→客户端)
协议ws:// / wss://http:// / https://
自动重连需手动实现内置支持
适用场景实时聊天、游戏通知、数据流

108. Geolocation API

定义 Geolocation API 用于获取用户的地理位置信息。

使用

if ('geolocation' in navigator) {
  navigator.geolocation.getCurrentPosition(
    (position) => {
      const { latitude, longitude, accuracy } = position.coords;
      console.log(`纬度: ${latitude}, 经度: ${longitude}, 精度: ${accuracy}m`);
    },
    (error) => {
      console.error('获取位置失败:', error.message);
    },
    {
      enableHighAccuracy: true,  // 高精度
      timeout: 5000,             // 超时时间
      maximumAge: 0,             // 不使用缓存
    }
  );

  // 持续监听位置变化
  const watchId = navigator.geolocation.watchPosition(successCallback, errorCallback);
  navigator.geolocation.clearWatch(watchId);
}

109. Notification API

定义 Notification API 用于在浏览器外显示系统级通知。

使用

// 请求权限
Notification.requestPermission().then(permission => {
  if (permission === 'granted') {
    new Notification('通知标题', {
      body: '这是通知内容',
      icon: '/icon.png',
      tag: 'unique-tag',       // 用于替换已有通知
      requireInteraction: true, // 不自动消失
    });
  }
});

110. Clipboard API

定义 Clipboard API 用于读取和写入剪贴板内容。

使用

// 写入剪贴板
async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log('已复制到剪贴板');
  } catch (err) {
    console.error('复制失败:', err);
  }
}

// 读取剪贴板
async function readFromClipboard() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('剪贴板内容:', text);
  } catch (err) {
    console.error('读取失败:', err);
  }
}

// 旧版兼容方式
function legacyCopy(text) {
  const textarea = document.createElement('textarea');
  textarea.value = text;
  document.body.appendChild(textarea);
  textarea.select();
  document.execCommand('copy');
  document.body.removeChild(textarea);
}

111. File API / Blob / FileReader

定义 File API 允许 Web 应用异步读取用户本地文件。

Blob

// 创建 Blob
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });

// Blob URL
const url = URL.createObjectURL(blob);
console.log(url); // "blob:http://example.com/xxx"

// 释放
URL.revokeObjectURL(url);

FileReader

const input = document.querySelector('input[type="file"]');

input.addEventListener('change', (e) => {
  const file = e.target.files[0];
  const reader = new FileReader();

  reader.onload = (event) => {
    console.log('文件内容:', event.target.result);
  };

  // 读取为文本
  reader.readAsText(file);

  // 读取为 DataURL(图片预览)
  // reader.readAsDataURL(file);

  // 读取为 ArrayBuffer
  // reader.readAsArrayBuffer(file);
});

FileList

const input = document.querySelector('input[type="file"][multiple]');

input.addEventListener('change', (e) => {
  const files = e.target.files; // FileList 对象
  for (let i = 0; i < files.length; i++) {
    console.log(`文件 ${i + 1}:`, files[i].name, files[i].size, files[i].type);
  }
});

112. Canvas

定义 Canvas 是 HTML5 提供的位图绘图 API,通过 JavaScript 在画布上绘制图形。

基本使用

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 绘制矩形
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);

// 绘制圆形
ctx.beginPath();
ctx.arc(200, 100, 50, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();

// 绘制文字
ctx.font = '20px Arial';
ctx.fillStyle = 'black';
ctx.fillText('Hello Canvas!', 50, 200);

// 绘制线条
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(300, 200);
ctx.strokeStyle = 'green';
ctx.lineWidth = 2;
ctx.stroke();

Canvas vs SVG

特性CanvasSVG
类型位图矢量图
APIJavaScriptXML/DOM
事件支持不支持(需手动计算)支持
性能大量元素时更好元素少时更好
适用场景游戏、图像处理图表、图标

113. WebGL

定义 WebGL 是基于 OpenGL ES 的 3D 图形 API,用于在 Canvas 上渲染 3D 图形。

基础示例

const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');

// 设置清除颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

// 实际开发中需要:
// 1. 编写着色器(Shader)
// 2. 创建缓冲区(Buffer)
// 3. 绘制图元

常用库

  • Three.js
  • Babylon.js
  • PixiJS(2D/3D)

114. 拖拽 API(Drag and Drop)

定义 拖拽 API 允许用户在页面上拖放元素。

使用

// 可拖拽元素
const draggable = document.getElementById('draggable');
draggable.draggable = true;

draggable.addEventListener('dragstart', (e) => {
  e.dataTransfer.setData('text/plain', e.target.id);
});

// 放置目标
const dropzone = document.getElementById('dropzone');

dropzone.addEventListener('dragover', (e) => {
  e.preventDefault(); // 必须阻止默认行为
  dropzone.classList.add('drag-over');
});

dropzone.addEventListener('drop', (e) => {
  e.preventDefault();
  const id = e.dataTransfer.getData('text/plain');
  const draggedEl = document.getElementById(id);
  dropzone.appendChild(draggedEl);
});

115. 全屏 API

定义 全屏 API 允许将页面或特定元素显示为全屏模式。

使用

const element = document.getElementById('fullscreen-element');

// 进入全屏
element.requestFullscreen().catch(err => {
  console.log('全屏失败:', err);
});

// 退出全屏
document.exitFullscreen();

// 监听全屏变化
document.addEventListener('fullscreenchange', () => {
  if (document.fullscreenElement) {
    console.log('进入全屏');
  } else {
    console.log('退出全屏');
  }
});

116. Page Visibility API

定义 Page Visibility API 用于检测页面是否可见(用户是否正在查看)。

使用

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    console.log('页面不可见,暂停视频');
    video.pause();
  } else {
    console.log('页面可见,恢复视频');
    video.play();
  }
});

// 兼容写法
const hidden = document.hidden || document.webkitHidden || document.msHidden;

适用场景

  • 页面不可见时暂停视频/动画
  • 停止不必要的数据轮询
  • 节省资源

117. Online/Offline API

定义 Online/Offline API 用于检测网络连接状态。

使用

// 当前状态
console.log(navigator.onLine ? '在线' : '离线');

// 监听状态变化
window.addEventListener('online', () => {
  console.log('网络已连接');
  showOnlineBanner();
});

window.addEventListener('offline', () => {
  console.log('网络已断开');
  showOfflineBanner();
});

118. WebRTC

定义 WebRTC(Web Real-Time Communication)是浏览器之间实时通信的技术,支持音视频通话和数据传输。

核心 API

// 获取本地媒体流
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(stream => {
    const video = document.querySelector('video');
    video.srcObject = stream;
  })
  .catch(err => {
    console.error('获取媒体流失败:', err);
  });

// RTCPeerConnection 用于建立 P2P 连接
const peerConnection = new RTCPeerConnection(iceServers);

通信流程

graph LR
    A[创建 PeerConnection] --> B[获取本地流]
    B --> C[创建 Offer]
    C --> D[通过信令服务器发送 Offer]
    D --> E[对方创建 Answer]
    E --> F[通过信令服务器返回 Answer]
    F --> G[ICE Candidate 交换]
    G --> H[P2P 连接建立]

十一、经典面试题

119. 从输入 URL 到页面呈现的完整过程

定义 这是前端面试中最经典的问题,涵盖了浏览器工作的全流程。

详细流程

graph TD
    A[输入 URL] --> B[DNS 解析]
    B --> C[TCP 三次握手]
    C --> D{HTTPS?}
    D -->|是| E[TLS 握手]
    D -->|否| F[发送 HTTP 请求]
    E --> F
    F --> G[服务器处理请求]
    G --> H[返回 HTTP 响应]
    H --> I[浏览器解析响应]
    I --> J{有缓存?}
    J -->|强缓存有效| K[使用缓存]
    J -->|协商缓存有效| L[返回 304, 使用缓存]
    J -->|无缓存| M[下载资源]
    K --> N[解析 HTML]
    L --> N
    M --> N
    N --> O[构建 DOM 树]
    O --> P[构建 CSSOM 树]
    P --> Q[构建 Render Tree]
    Q --> R[Layout 布局]
    R --> S[Paint 绘制]
    S --> T[Composite 合成]
    T --> U[显示到屏幕]

详细步骤说明

  1. DNS 解析

    • 浏览器缓存 → 系统缓存 → 路由器缓存 → ISP DNS → 根域名服务器
    • 最终获取 IP 地址
  2. TCP 连接

    • 三次握手建立 TCP 连接
    • 如果是 HTTPS,还需要 TLS 握手
  3. 发送 HTTP 请求

    • 构建请求报文(请求行、请求头、请求体)
    • 通过 TCP 连接发送
  4. 服务器处理

    • Nginx 反向代理
    • 后端服务处理业务逻辑
    • 查询数据库
    • 返回响应
  5. 浏览器接收响应

    • 解析响应头和响应体
    • 检查缓存
    • 根据 Content-Type 处理响应
  6. 渲染页面

    • 解析 HTML 构建 DOM 树
    • 解析 CSS 构建 CSSOM 树
    • 执行 JavaScript(可能阻塞解析)
    • 构建 Render Tree
    • Layout → Paint → Composite
    • 显示到屏幕

120. XML 与 JSON 的区别

对比表格

维度XMLJSON
语法标签式,需要闭合标签键值对,基于 JavaScript 对象语法
体积较大(标签冗余)较小
解析速度较慢(需要解析 DOM)较快(原生 JSON.parse)
数据类型只有字符串支持字符串、数字、布尔、数组、对象、null
可读性较清晰(标签语义化)较简洁
支持注释✅ 支持❌ 不支持
使用场景SOAP、配置文件RESTful API、数据交换

示例对比

<!-- XML -->
<user>
  <name>Alice</name>
  <age>25</age>
  <roles>
    <role>admin</role>
    <role>editor</role>
  </roles>
</user>
// JSON
{
  "name": "Alice",
  "age": 25,
  "roles": ["admin", "editor"]
}

121. 事件循环在浏览器与 Node.js 中的差异

对比表格

维度浏览器Node.js
事件循环模型单线程事件循环基于 libuv 的多线程事件循环
宏任务setTimeout, setInterval, requestAnimationFrame, I/OsetTimeout, setInterval, setImmediate, I/O
微任务Promise.then, MutationObserver, queueMicrotaskPromise.then, queueMicrotask, process.nextTick
process.nextTick❌ 不支持✅ 优先级最高的微任务
setImmediate❌ 不支持✅ 在 I/O 事件后执行
线程模型JS 单线程,Worker 辅助主线程单线程,libuv 线程池处理 I/O
渲染阶段每次宏任务间可能触发页面渲染不涉及页面渲染

执行顺序差异

// 在浏览器中
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('promise'));
// 输出: promise → setTimeout

// 在 Node.js 中
setImmediate(() => console.log('setImmediate'));
setTimeout(() => console.log('setTimeout'), 0);
// 输出顺序不确定(取决于事件循环进入时机)

process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
// 输出: nextTick → promise (nextTick 优先级高于 Promise)

122. Quirks 模式与 Standards 模式

定义

  • Standards 模式(标准模式):浏览器按照 W3C 标准解析和渲染页面
  • Quirks 模式(怪异模式):浏览器模拟老式浏览器的行为,以兼容老旧网站

触发条件

<!-- Standards 模式(推荐) -->
<!DOCTYPE html>

<!-- Quirks 模式(不要这样做) -->
<!-- 没有 DOCTYPE -->
<!-- 或者 DOCTYPE 格式不正确 -->

差异对比

特性Standards 模式Quirks 模式
盒模型W3C 标准(content-box)IE 盒模型(border-box)
行内元素宽高不支持设置支持设置
表格字体继承正常继承不继承
百分比高度正常计算可能不生效
图片对齐基线对齐底部对齐
CSS 属性大小写区分大小写不区分

Almost Standards 模式

  • 使用过渡 DOCTYPE 触发
  • 大部分行为遵循标准
  • 但在某些方面(如图片在表格单元格中的布局)使用怪异模式

123. IE 低版本的 hasLayout 问题

定义 hasLayout 是 IE 浏览器(IE7 及以下)内部的概念,用于确定元素如何计算尺寸和位置。

触发 hasLayout 的属性

/* 触发 hasLayout */
display: inline-block;
position: absolute;
float: left/right;
width: any value;
height: any value;
zoom: 1;        /* IE 特有,最常用的触发方式 */

常见问题

  • 父元素没有清除浮动,导致子元素布局错乱
  • 元素没有 hasLayout,无法正确计算尺寸

解决方案

/* 清除浮动的经典方案 */
.clearfix {
  *zoom: 1; /* 触发 IE hasLayout */
}
.clearfix::after {
  content: '';
  display: table;
  clear: both;
}

124. Chrome 显示小于 12px 文字

问题 Chrome 浏览器默认不允许显示小于 12px 的文字。

解决方案

  1. CSS transform 缩放
.small-text {
  font-size: 16px;
  transform: scale(0.75); /* 16 * 0.75 = 12 */
  transform-origin: left center;
  display: inline-block;
}
  1. 使用 SVG
<svg width="100" height="20">
  <text x="0" y="15" font-size="10">小字文本</text>
</svg>
  1. 使用图片
  • 将小文字渲染为图片显示

十二、补充说明

浏览器兼容性测试

测试工具

  • BrowserStack:在线跨浏览器测试平台
  • Can I Use(caniuse.com):查询各浏览器对 API 的支持情况
  • Lighthouse:性能和质量审计
  • Selenium:自动化跨浏览器测试

测试流程

  1. 确定目标浏览器范围
  2. 使用 Can I Use 查询 API 兼容性
  3. 在目标浏览器中手动测试关键功能
  4. 使用自动化测试工具进行回归测试
  5. 针对不兼容的浏览器提供 Polyfill 或降级方案