一、浏览器从输入URL到渲染页面
1.1 完整流程
阶段一:网络请求
- URL解析:解析协议(HTTP/HTTPS)、域名、端口、路径和查询参数
- 强缓存检查:浏览器检查本地缓存是否命中强缓存(
Cache-Control、Expires) - DNS解析:将域名解析为IP地址(浏览器缓存 → 操作系统缓存 → 本地DNS服务器 → 递归查询(根域名服务器 -> 顶级域名服务器 -> 权威域名服务器))
- TCP三次握手:建立与服务器的可靠连接(客户端发送 SYN 报文 → 服务器响应 SYN+ACK 报文 → 客户端发送 ACK 报文 → 服务器确认 ACK 报文)
- HTTPS握手(如果是HTTPS):协商加密方式、验证证书、交换密钥(客户端发送 Client Hello → 服务器发送 Server Hello 和证书 → 客户端验证证书并生成会话密钥 → 客户端发送加密的会话密钥 → 服务器确认会话密钥)
- 发送HTTP请求:发送请求行(方法、路径、HTTP版本)、请求头(Host、User-Agent、Accept等)、请求体(如果是 POST 等方法)
- 服务器处理:接收请求、解析请求头和请求体、处理业务逻辑、生成响应
- 接收HTTP响应:接收状态行(HTTP版本、状态码、状态文本)、响应头(Content-Type、Content-Length等)、响应体(可能触发协商缓存验证)
阶段二:浏览器解析
- HTML解析:解析HTML文档,构建DOM(Document Object Model)树
- CSS解析:解析CSS样式,构建CSSOM(CSS Object Model)树
- 样式计算:计算每个DOM元素的最终样式(继承、层叠、优先级)
- 构建布局树:将DOM树与CSSOM树结合,生成Layout Tree(只包含可见元素)
阶段三:浏览器渲染
- 构建图层树:根据布局树和元素的堆叠上下文,构建Layer Tree
- 生成绘制列表:为每个图层生成绘制指令
- 栅格化:将绘制指令转换为像素信息(GPU加速)
- 合成:将所有图层的像素信息合成到一个图层
- 显示:将合成后的图层显示在屏幕上
阶段四:后续交互
- JavaScript执行:执行脚本,可能修改DOM和样式,触发重排/重绘
- 事件处理:处理用户交互事件(点击、滚动、输入等)
- 页面更新:根据JavaScript操作更新页面内容
1.2 渲染流程关键点
阻塞渲染的资源:
- CSS:阻塞渲染,需尽快加载关键CSS
- JavaScript:阻塞DOM解析和渲染,可使用
async/defer异步加载
关键渲染路径:
- HTML → DOM
- CSS → CSSOM
- DOM + CSSOM → Render Tree
- 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 缓存工作流程
- 浏览器发起请求时,首先检查强缓存
- 如果命中强缓存,直接使用本地缓存,不发送请求
- 如果未命中强缓存,发送请求到服务器,使用协商缓存
- 服务器根据请求头(If-Modified-Since 或 If-None-Match)判断资源是否变化
- 如果资源未变化,返回 304 Not Modified,浏览器使用本地缓存
- 如果资源已变化,返回 200 OK 和新资源
3.3 缓存策略最佳实践
- 静态资源(CSS、JS、图片):
- 使用强缓存,设置较长的
max-age - 采用文件指纹(如
app.abc123.js)确保版本更新
- 使用强缓存,设置较长的
- API 接口:
- 使用协商缓存,设置
ETag或Last-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接口 | 协商缓存 | 设置 ETag 或 Last-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复杂度 | 简单 | 简单 | 简单 | 复杂 |
| 数据类型 | 字符串 | 字符串 | 字符串 | 任意类型 |
| 异步操作 | 同步 | 同步 | 同步 | 异步 |
5.2 Cookie
特点:
- 用于在浏览器和服务器之间传递数据
- 每次请求都会自动携带
- 有路径和域名限制
- 可设置过期时间
使用场景:
- 会话管理:保存登录状态
- 个性化设置:用户偏好
- 追踪用户行为
示例:
// 设置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