前端面试准备指南七之性能优化篇


一、浏览器从输入URL到渲染页面

1.1 完整流程

阶段一:网络请求

  1. URL解析:解析协议(HTTP/HTTPS)、域名、端口、路径和查询参数
  2. 强缓存检查:浏览器检查本地缓存是否命中强缓存(Cache-ControlExpires
  3. DNS解析:将域名解析为IP地址(浏览器缓存 → 操作系统缓存 → 本地DNS服务器 → 递归查询(根域名服务器 -> 顶级域名服务器 -> 权威域名服务器))
  4. TCP三次握手:建立与服务器的可靠连接(客户端发送 SYN 报文 → 服务器响应 SYN+ACK 报文 → 客户端发送 ACK 报文 → 服务器确认 ACK 报文)
  5. HTTPS握手(如果是HTTPS):协商加密方式、验证证书、交换密钥(客户端发送 Client Hello → 服务器发送 Server Hello 和证书 → 客户端验证证书并生成会话密钥 → 客户端发送加密的会话密钥 → 服务器确认会话密钥)
  6. 发送HTTP请求:发送请求行(方法、路径、HTTP版本)、请求头(Host、User-Agent、Accept等)、请求体(如果是 POST 等方法)
  7. 服务器处理:接收请求、解析请求头和请求体、处理业务逻辑、生成响应
  8. 接收HTTP响应:接收状态行(HTTP版本、状态码、状态文本)、响应头(Content-Type、Content-Length等)、响应体(可能触发协商缓存验证)

阶段二:浏览器解析

  1. HTML解析:解析HTML文档,构建DOM(Document Object Model)树
  2. CSS解析:解析CSS样式,构建CSSOM(CSS Object Model)树
  3. 样式计算:计算每个DOM元素的最终样式(继承、层叠、优先级)
  4. 构建布局树:将DOM树与CSSOM树结合,生成Layout Tree(只包含可见元素)

阶段三:浏览器渲染

  1. 构建图层树:根据布局树和元素的堆叠上下文,构建Layer Tree
  2. 生成绘制列表:为每个图层生成绘制指令
  3. 栅格化:将绘制指令转换为像素信息(GPU加速)
  4. 合成:将所有图层的像素信息合成到一个图层
  5. 显示:将合成后的图层显示在屏幕上

阶段四:后续交互

  • JavaScript执行:执行脚本,可能修改DOM和样式,触发重排/重绘
  • 事件处理:处理用户交互事件(点击、滚动、输入等)
  • 页面更新:根据JavaScript操作更新页面内容

1.2 渲染流程关键点

阻塞渲染的资源

  • CSS:阻塞渲染,需尽快加载关键CSS
  • JavaScript:阻塞DOM解析和渲染,可使用 async / defer 异步加载

关键渲染路径

  1. HTML → DOM
  2. CSS → CSSOM
  3. DOM + CSSOM → Render Tree
  4. Render Tree → Layout → Paint → Composite

优化策略

  • 内联关键CSS(Critical CSS)
  • 异步加载非关键CSS
  • 异步加载JavaScript
  • 减少DOM节点数量
  • 优化CSS选择器

二、网络优化

2.1 减少HTTP请求

  • 合并文件:将多个CSS/JS文件合并为一个
  • CSS Sprites:将多个小图片合并为一张大图片
  • 内联关键CSS:将首屏所需的CSS内联到HTML中
  • 减少DOM元素:简化页面结构,减少元素数量
  • 使用字体图标:替代多个小图标,减少图片请求

2.2 使用CDN

  • 选择合适的CDN提供商:根据目标用户分布选择
  • 合理配置缓存策略:设置适当的缓存时间
  • 启用HTTPS:确保CDN传输安全
  • 多域名CDN:突破浏览器并发请求限制

2.3 启用HTTP/2

  • 多路复用:在一个连接上同时发送多个请求
  • 服务器推送:提前推送关键资源
  • 头部压缩:减少头部传输大小
  • 二进制分帧:提高传输效率

2.4 压缩资源

  • Gzip/Brotli压缩:压缩HTML、CSS、JS文件
  • 图片压缩:使用工具压缩图片大小
  • 代码压缩:移除代码中的空白、注释等
  • 字体压缩:只包含使用的字符

2.5 预加载和懒加载

预加载

  • <link rel="preload">:预加载关键资源
  • <link rel="prefetch">:预加载可能需要的资源
  • <link rel="dns-prefetch">:预解析DNS

懒加载

  • 图片懒加载:滚动到可视区域再加载
  • 组件懒加载:需要时再加载组件
  • 路由懒加载:按需加载路由

2.6 其他网络优化

  • 减少DNS查询:使用DNS缓存
  • 优化TCP连接:启用TCP Fast Open
  • 减少重定向:避免不必要的301/302跳转
  • 使用QUIC/HTTP/3:提高传输速度
  • 监控和分析:使用性能监控工具分析网络请求

三、HTTP缓存

3.1 缓存类型

3.1.1 强缓存

  • Expires:HTTP/1.0 字段,基于服务器时间,易受时间同步问题影响
  • Cache-Control:HTTP/1.1 字段,优先级高于 Expires
    • no-cache:强制进行协商缓存
    • no-store:完全不缓存,每次都从服务器获取
    • max-age:缓存有效时间(秒)
    • s-maxage:共享缓存(如 CDN)的有效时间
    • public:可以被所有缓存(客户端、代理服务器等)
    • private:只能被客户端缓存
    • must-revalidate:缓存过期后必须重新验证

3.1.2 协商缓存

  • Last-Modified / If-Modified-Since:基于文件最后修改时间
    • 优点:实现简单,开销小
    • 缺点:精度低(只能精确到秒),无法感知文件内容变化
  • ETag / If-None-Match:基于文件内容的唯一标识
    • 优点:精度高,能感知内容变化
    • 缺点:计算开销大

3.2 缓存工作流程

  1. 浏览器发起请求时,首先检查强缓存
    • 如果命中强缓存,直接使用本地缓存,不发送请求
    • 如果未命中强缓存,发送请求到服务器,使用协商缓存
  2. 服务器根据请求头(If-Modified-Since 或 If-None-Match)判断资源是否变化
    • 如果资源未变化,返回 304 Not Modified,浏览器使用本地缓存
    • 如果资源已变化,返回 200 OK 和新资源

3.3 缓存策略最佳实践

  • 静态资源(CSS、JS、图片):
    • 使用强缓存,设置较长的 max-age
    • 采用文件指纹(如 app.abc123.js)确保版本更新
  • API 接口
    • 使用协商缓存,设置 ETagLast-Modified
    • 对于频繁变化的数据,使用 Cache-Control: no-cache
  • HTML 文件
    • 通常设置 Cache-Control: no-cache,确保每次都检查最新版本

3.4 缓存进阶

静态资源版本控制

  • 文件指纹:使用 [contenthash](推荐),如 app.abc123.js
  • URL参数:使用 ?v=1.0.0(简单但不利于缓存)

Service Worker

  • 实现离线缓存,提供更好的用户体验
  • 支持后台同步,可在网络恢复后同步数据
  • 可拦截网络请求,实现渐进式网络应用(PWA)

缓存策略选择指南

资源类型 推荐策略 说明
静态资源(CSS、JS、图片) 强缓存 + 文件指纹 max-age=31536000
API接口 协商缓存 设置 ETagLast-Modified
HTML文件 no-cache 确保每次都检查最新版本
敏感数据 no-store 完全不缓存

四、代码优化

4.1 事件代理

原理:利用事件冒泡,将子元素的事件绑定到父元素上

优点

  • 减少事件监听器数量,提高性能
  • 动态添加的元素也能响应事件
  • 简化代码,便于维护

示例

// 传统方式:为每个li添加事件监听器
document.querySelectorAll("ul li").forEach(li => {
    li.addEventListener("click", handleClick);
});

// 事件代理:只为ul添加一个事件监听器
document.querySelector("ul").addEventListener("click", e => {
    if (e.target.tagName === "LI") {
        handleClick(e);
    }
});

4.2 事件的节流和防抖

防抖(Debounce)

  • 原理:触发事件后,等待一段时间再执行,如果期间再次触发,则重新计时
  • 应用场景:搜索输入、窗口 resize、滚动事件

示例

function debounce(func, wait) {
    let timeout;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, arguments), wait);
    };
}

节流(Throttle)

  • 原理:限制事件执行的频率,固定时间内只执行一次
  • 应用场景:滚动事件、游戏中的鼠标移动、窗口 resize

示例(时间戳版本 - 头部节流)

function throttle(func, limit) {
    let lastTime = 0;
    return function () {
        const now = Date.now();
        if (now - lastTime >= limit) {
            func.apply(this, arguments);
            lastTime = now;
        }
    };
}

示例(定时器版本 - 尾部节流)

function throttle(func, limit) {
    let timeout;
    return function () {
        if (!timeout) {
            timeout = setTimeout(() => {
                func.apply(this, arguments);
                timeout = null;
            }, limit);
        }
    };
}

区别

  • 头部节流:事件触发时立即执行,之后在限制时间内不再执行
  • 尾部节流:事件触发后等待限制时间再执行,如果期间再次触发则重新计时

4.3 页面的回流和重绘

回流(Reflow)

  • 定义:DOM元素的几何属性发生变化,导致浏览器重新计算布局
  • 触发条件:改变元素尺寸、位置、添加/删除元素、修改字体大小等
  • 影响:性能开销大,应尽量避免

重绘(Repaint)

  • 定义:元素的外观发生变化,但几何属性不变
  • 触发条件:改变颜色、背景色、 visibility 等
  • 影响:性能开销较小

优化策略

  • 使用 CSS transform 代替位置属性
  • 批量修改样式,避免频繁操作DOM
  • 使用文档片段(DocumentFragment)
  • 避免使用 table 布局
  • 使用 will-change 提示浏览器

4.4 EventLoop事件循环机制

执行机制

  • 同步任务:直接执行
  • 异步任务:
    • 宏任务(Macro Task):setTimeout、setInterval、I/O、事件回调
    • 微任务(Micro Task):Promise、MutationObserver
  • 执行顺序:同步任务 → 微任务 → 宏任务 → 微任务 → …

示例

console.log("1"); // 同步任务

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

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

console.log("4"); // 同步任务

// 执行顺序:1 → 4 → 3 → 2

4.5 其他代码优化

DOM操作优化

  • 缓存DOM查询结果
  • 批量操作DOM
  • 使用虚拟DOM
  • 减少DOM层级

JavaScript优化

  • 使用 let/const 代替 var
  • 避免使用 eval
  • 合理使用闭包
  • 减少全局变量
  • 使用箭头函数和模板字符串

算法优化

  • 选择合适的数据结构
  • 优化时间复杂度和空间复杂度
  • 使用记忆化(Memoization)
  • 避免重复计算

资源加载优化

  • 异步加载脚本
  • 按需加载模块
  • 延迟加载非关键资源

代码风格优化

  • 保持代码简洁
  • 适当添加注释
  • 遵循代码规范
  • 模块化开发

五、本地存储

5.1 存储类型对比

特性 Cookie localStorage sessionStorage IndexedDB
存储大小 4KB 5MB 5MB 无限制
有效期 可设置过期时间 永久,除非手动清除 会话结束 永久,除非手动清除
作用域 所有同源窗口 所有同源窗口 当前窗口 所有同源窗口
与服务器通信 自动携带 不自动携带 不自动携带 不自动携带
API复杂度 简单 简单 简单 复杂
数据类型 字符串 字符串 字符串 任意类型
异步操作 同步 同步 同步 异步

特点

  • 用于在浏览器和服务器之间传递数据
  • 每次请求都会自动携带
  • 有路径和域名限制
  • 可设置过期时间

使用场景

  • 会话管理:保存登录状态
  • 个性化设置:用户偏好
  • 追踪用户行为

示例

// 设置cookie(包含安全属性)
document.cookie =
    "username=John; expires=Fri, 31 Dec 2026 23:59:59 GMT; path=/; HttpOnly; Secure; SameSite=Strict";

// 读取cookie(获取所有cookie字符串)
const cookieValue = document.cookie;

// 解析cookie
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(";").shift();
}

// 删除cookie
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";

Cookie安全属性

  • HttpOnly:防止JavaScript读取,防范XSS攻击
  • Secure:仅在HTTPS连接下发送
  • SameSite:限制跨站发送(Strict/Lax/None)

5.3 localStorage

特点

  • 永久存储,除非手动清除
  • 存储在客户端,不与服务器通信
  • 同源窗口共享
  • 存储大小约5MB

使用场景

  • 持久化用户偏好设置
  • 缓存不常变化的数据
  • 本地存储用户数据

示例

// 存储数据
localStorage.setItem("username", "John");

// 读取数据
const username = localStorage.getItem("username");

// 删除数据
localStorage.removeItem("username");

// 清空所有数据
localStorage.clear();

5.4 sessionStorage

特点

  • 会话期间有效,窗口关闭后清除
  • 存储在客户端,不与服务器通信
  • 仅当前窗口可见
  • 存储大小约5MB

使用场景

  • 临时表单数据
  • 会话期间的状态管理
  • 敏感信息,不需要持久化

示例

// 存储数据
sessionStorage.setItem("tempData", "value");

// 读取数据
const tempData = sessionStorage.getItem("tempData");

5.5 IndexedDB

特点

  • 异步操作,不阻塞主线程
  • 存储大小无限制(受用户设备空间限制)
  • 支持复杂查询和事务
  • 存储结构化数据

使用场景

  • 离线应用数据存储
  • 大量结构化数据
  • 需要复杂查询的数据
  • 图片、视频等大型文件

示例

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

request.onupgradeneeded = function (event) {
    const db = event.target.result;
    // 创建对象存储空间
    const objectStore = db.createObjectStore("users", { keyPath: "id" });
    // 创建索引
    objectStore.createIndex("name", "name", { unique: false });
};

request.onsuccess = function (event) {
    const db = event.target.result;
    // 存储数据
    const transaction = db.transaction(["users"], "readwrite");
    const objectStore = transaction.objectStore("users");
    objectStore.add({ id: 1, name: "John", age: 30 });
};

5.6 本地存储最佳实践

安全性

  • 不要存储敏感信息(如密码)
  • 敏感数据应加密存储
  • 注意XSS攻击,避免恶意脚本读取存储数据

性能

  • 避免存储大量数据,影响页面加载速度
  • 定期清理不需要的数据
  • 对于大型数据,优先使用IndexedDB

兼容性

  • 检查浏览器支持情况
  • 提供降级方案

使用建议

  • 临时数据:sessionStorage
  • 持久化小数据:localStorage
  • 持久化大数据:IndexedDB
  • 与服务器通信:Cookie

文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录