浏览器原理
一、浏览器渲染原理
1. 浏览器渲染流程
主要步骤:
- 解析 HTML:构建 DOM 树
- 解析 CSS:构建 CSSOM 树
- 合并:将 DOM 和 CSSOM 合并成渲染树(Render Tree)
- 布局(Layout/Reflow):计算元素的位置和大小
- 绘制(Paint):填充像素
- 合成(Composite):将图层合并
2. DOM 树构建
HTML 解析过程:
- 字节流 → 字符流 → Token → Node → DOM 树
- 浏览器使用状态机解析 HTML
- 遇到 script 标签会阻塞解析,执行 JavaScript
3. CSSOM 树构建
CSS 解析过程:
- 字节流 → 字符流 → Token → Node → CSSOM 树
- CSS 解析不会阻塞 DOM 解析,但会阻塞渲染
- CSS 规则从右到左匹配(提高匹配效率)
4. 渲染树(Render Tree)
渲染树的特点:
- 只包含需要渲染的元素(display: none 不包含)
- 每个节点都有对应的 CSS 样式
- 只包含可见元素
5. 布局(Layout/Reflow)
布局过程:
- 计算元素的位置和大小
- 从根节点开始递归计算
- 使用盒模型计算
触发重排(Reflow)的操作:
- 修改 DOM 结构
- 修改元素样式(width、height、position 等)
- 窗口大小变化
- 字体大小变化
6. 绘制(Paint)
绘制过程:
- 填充像素
- 绘制文本、颜色、图片等
- 在多个图层上绘制
触发重绘(Repaint)的操作:
- 修改颜色、背景色
- 修改边框样式
- 不影响布局的样式变化
7. 合成(Composite)
合成过程:
- 将多个图层合并
- 使用 GPU 加速
- 避免整个页面重排和重绘
二、重排和重绘
1. 重排(Reflow)
什么是重排? 当 DOM 结构或样式变化,导致浏览器重新计算元素位置和大小。
触发重排的操作:
javascript
// 修改 DOM 结构
element.appendChild(newElement);
// 修改元素样式
element.style.width = '100px';
element.style.height = '100px';
element.style.position = 'absolute';
// 获取布局信息(强制重排)
const width = element.offsetWidth;
const height = element.offsetHeight;优化重排:
- 批量修改 DOM(使用 DocumentFragment)
- 避免频繁读取布局属性
- 使用 transform 代替修改位置属性
- 使用 visibility 代替 display: none
javascript
// 不推荐:多次重排
element.style.left = '10px';
element.style.top = '10px';
element.style.width = '100px';
// 推荐:一次重排
element.style.cssText = 'left: 10px; top: 10px; width: 100px;';
// 或者使用 class
element.className = 'new-style';2. 重绘(Repaint)
什么是重绘? 当元素样式变化,但不影响布局,浏览器只需要重新绘制。
触发重绘的操作:
javascript
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.border = '1px solid black';优化重绘:
- 减少重绘次数
- 使用 CSS3 动画(GPU 加速)
- 使用 will-change 提示浏览器优化
三、事件循环(Event Loop)
1. JavaScript 执行机制
单线程:
- JavaScript 是单线程的,同一时间只能执行一个任务
- 通过事件循环机制处理异步任务
执行栈:
- 用于存储同步代码的执行上下文
- 先进后出(LIFO)
任务队列:
- 宏任务队列(MacroTask):setTimeout、setInterval、I/O、UI 渲染
- 微任务队列(MicroTask):Promise.then、queueMicrotask、MutationObserver
2. 事件循环流程
执行顺序:
- 执行同步代码
- 执行微任务队列(全部执行完)
- 执行一个宏任务
- 执行微任务队列(全部执行完)
- 重复步骤 3-4
javascript
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
// 输出:1, 4, 3, 2执行过程:
- 执行
console.log(1)→ 输出 1 - 遇到
setTimeout,加入宏任务队列 - 遇到
Promise.resolve().then(),加入微任务队列 - 执行
console.log(4)→ 输出 4 - 同步代码执行完毕,执行微任务队列 → 输出 3
- 执行宏任务队列 → 输出 2
3. 微任务和宏任务
微任务(MicroTask):
Promise.then()Promise.catch()Promise.finally()queueMicrotask()MutationObserver
宏任务(MacroTask):
setTimeoutsetIntervalsetImmediate(Node.js)I/O- UI 渲染
requestAnimationFrame
javascript
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
}, 0);
Promise.resolve().then(() => {
console.log(4);
setTimeout(() => {
console.log(5);
}, 0);
});
console.log(6);
// 输出:1, 6, 4, 2, 3, 5四、内存管理
1. 内存生命周期
分配内存:
- 声明变量、函数、对象时分配内存
使用内存:
- 读写变量、调用函数
释放内存:
- 垃圾回收机制自动释放
2. 垃圾回收
标记清除(Mark and Sweep):
- 标记所有从根节点可达的对象
- 清除未标记的对象(垃圾)
引用计数:
- 记录每个对象被引用的次数
- 当引用次数为 0 时回收
- 无法处理循环引用
3. 内存泄漏
常见内存泄漏:
1. 全局变量
javascript
// 泄漏
function leak() {
window.data = new Array(1000000);
}
// 解决:使用局部变量或及时清理2. 闭包
javascript
// 泄漏
function outer() {
const data = new Array(1000000);
return function inner() {
console.log('inner');
};
}
// 解决:避免不必要的闭包3. DOM 引用
javascript
// 泄漏
const element = document.getElementById('myElement');
const data = { element };
// 即使删除 DOM,data.element 仍引用它
element.remove();
// 解决:删除 DOM 前清理引用
data.element = null;4. 定时器未清理
javascript
// 泄漏
const timer = setInterval(() => {
console.log('running');
}, 1000);
// 解决:及时清理
clearInterval(timer);5. 事件监听器未移除
javascript
// 泄漏
element.addEventListener('click', handleClick);
// 解决:移除事件监听器
element.removeEventListener('click', handleClick);五、性能优化
1. 资源加载优化
图片优化:
- 使用适当的图片格式(WebP、AVIF)
- 图片懒加载
- 响应式图片(srcset)
- 压缩图片
脚本优化:
- 使用 defer 或 async
- 代码分割(Code Splitting)
- Tree Shaking
- 压缩和混淆
html
<!-- defer:DOM 解析完成后执行 -->
<script src="app.js" defer></script>
<!-- async:下载完成后立即执行 -->
<script src="analytics.js" async></script>2. 渲染优化
CSS 优化:
- 避免使用 @import
- 内联关键 CSS
- 避免深层嵌套选择器
- 使用 transform 和 opacity(GPU 加速)
JavaScript 优化:
- 避免阻塞渲染
- 使用 requestIdleCallback 处理非关键任务
- 使用 requestAnimationFrame 优化动画
javascript
// 使用 requestAnimationFrame 优化动画
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
animate();3. 缓存策略
浏览器缓存:
- 强缓存:Cache-Control、Expires
- 协商缓存:ETag、Last-Modified
HTTP 缓存头:
http
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT4. 网络优化
CDN:
- 使用 CDN 加速资源加载
- 减少延迟
HTTP/2:
- 多路复用
- 服务器推送
- 头部压缩
预加载和预渲染:
html
<!-- 预加载资源 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预渲染页面 -->
<link rel="prerender" href="https://example.com/page">六、安全
1. XSS(跨站脚本攻击)
XSS 类型:
- 存储型 XSS:恶意脚本存储在服务器
- 反射型 XSS:恶意脚本在 URL 中
- DOM 型 XSS:客户端脚本修改 DOM
防护措施:
- 输入验证和过滤
- 输出转义
- 使用 Content-Security-Policy(CSP)
- 使用 HttpOnly Cookie
2. CSRF(跨站请求伪造)
防护措施:
- 使用 CSRF Token
- 验证 Referer
- 使用 SameSite Cookie
javascript
// CSRF Token
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/data', {
method: 'POST',
headers: {
'X-CSRF-Token': token
}
});3. 其他安全问题
点击劫持:
- 使用 X-Frame-Options
中间人攻击:
- 使用 HTTPS
敏感信息泄露:
- 不要在客户端存储敏感信息
- 使用 HTTPS 传输数据
七、常见面试题
1. 浏览器渲染过程?
- 解析 HTML 构建 DOM 树
- 解析 CSS 构建 CSSOM 树
- 合并成渲染树
- 布局(计算位置和大小)
- 绘制(填充像素)
- 合成(合并图层)
2. 重排和重绘的区别?
- 重排:重新计算元素位置和大小(更耗性能)
- 重绘:重新绘制元素样式(相对较轻)
重排一定会触发重绘,但重绘不一定触发重排。
3. 如何优化页面性能?
- 资源加载优化:压缩、懒加载、CDN
- 渲染优化:减少重排重绘、使用 GPU 加速
- JavaScript 优化:代码分割、异步加载
- 缓存策略:合理使用浏览器缓存
- 网络优化:使用 HTTP/2、预加载
4. 事件循环机制?
JavaScript 是单线程的,通过事件循环处理异步任务。执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → ...
5. 内存泄漏如何排查?
- 使用 Chrome DevTools 的 Memory 面板
- 使用 Performance 面板分析内存使用
- 检查全局变量、闭包、DOM 引用、定时器等