一、浏览器渲染机制
1. 浏览器的渲染流程
完整渲染流程:
1. 解析HTML → 构建DOM树
2. 解析CSS → 构建CSSOM树
3. DOM + CSSOM → 构建渲染树(Render Tree)
4. 布局(Layout/Reflow) → 计算元素位置和大小
5. 绘制(Paint) → 绘制像素
6. 合成(Composite) → 合成图层到屏幕
详细过程:
html
<!DOCTYPE html>
<html>
<head>
<style>
.box { width: 100px; height: 100px; background: red; }
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
渲染步骤解析:
-
HTML解析 → DOM树
- 字节 → 字符 → 令牌 → 节点 → DOM树
- 遇到
<script>会阻塞解析(除非有async/defer) - 遇到
<img>等资源,异步加载
-
CSS解析 → CSSOM树
- CSS会阻塞渲染,但不会阻塞DOM解析
- CSS加载完成前不会构建渲染树
-
构建渲染树
- 遍历DOM树,匹配CSSOM规则
- 跳过不可见元素(
display: none) visibility: hidden的元素会保留在渲染树中
-
布局(Layout)
- 计算元素的几何信息(位置、大小)
- 第一次布局称为"首次布局"
- 后续布局称为"重排(Reflow)"
-
绘制(Paint)
- 将渲染树转换为屏幕上的像素
- 分多个图层绘制
-
合成(Composite)
- 将多个图层合成最终图像
2. 回流(Reflow)与重绘(Repaint)
回流(Reflow): 元素的几何属性变化,需要重新计算布局
触发回流的操作:
javascript
// 1. 添加/删除可见DOM元素
document.body.appendChild(newElement);
// 2. 元素位置、尺寸改变
element.style.width = '200px';
element.style.height = '200px';
element.style.margin = '10px';
// 3. 内容改变
element.textContent = '新内容';
// 4. 浏览器窗口尺寸改变
window.addEventListener('resize', handler);
// 5. 读取某些属性(强制同步布局)
const height = element.offsetHeight;
const width = element.clientWidth;
const rect = element.getBoundingClientRect();
重绘(Repaint): 元素样式改变但不影响布局
javascript
// 只触发重绘的操作
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.visibility = 'hidden';
element.style.outline = '1px solid red';
性能影响: 回流 > 重绘 > 合成
优化策略:
javascript
// ❌ 多次操作触发多次回流
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';
// ✅ 使用class一次性修改
element.className = 'new-styles';
// ✅ 使用cssText
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// ❌ 读写分离问题
element.style.width = '100px';
const height = element.offsetHeight; // 强制回流
element.style.height = '100px';
// ✅ 批量读取,批量写入
const height = element.offsetHeight;
const width = element.offsetWidth;
element.style.width = '100px';
element.style.height = '100px';
// ✅ 使用文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
ul.appendChild(fragment); // 只触发一次回流
// ✅ 使用transform代替left/top
// ❌ 触发回流
element.style.left = '100px';
// ✅ 只触发合成
element.style.transform = 'translateX(100px)';
// ✅ 使用requestAnimationFrame
function animate() {
element.style.transform = `translateX(${pos}px)`;
requestAnimationFrame(animate);
}
3. 浏览器的事件循环机制
事件循环组成:
调用栈(Call Stack)
↓
Web APIs (setTimeout, DOM事件等)
↓
任务队列 {
宏任务队列: setTimeout, setInterval, I/O, UI渲染
微任务队列: Promise, MutationObserver, queueMicrotask
}
执行顺序:
javascript
console.log('1. 同步代码');
setTimeout(() => {
console.log('2. 宏任务 - setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('3. 微任务 - Promise');
});
console.log('4. 同步代码');
// 输出顺序:
// 1. 同步代码
// 4. 同步代码
// 3. 微任务 - Promise
// 2. 宏任务 - setTimeout
详细执行流程:
javascript
console.log('Start');
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('Promise in setTimeout');
});
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => {
console.log('setTimeout in Promise');
}, 0);
})
.then(() => {
console.log('Promise 2');
});
console.log('End');
// 输出顺序:
// Start
// End
// Promise 1
// Promise 2
// setTimeout 1
// Promise in setTimeout
// setTimeout in Promise
执行规则:
- 执行同步代码
- 执行所有微任务
- 执行一个宏任务
- 执行所有微任务
- 重复3-4
常见宏任务与微任务:
| 宏任务 | 微任务 |
|---|---|
| setTimeout | Promise.then/catch/finally |
| setInterval | MutationObserver |
| setImmediate (Node) | queueMicrotask |
| I/O | process.nextTick (Node) |
| UI渲染 |
4. 浏览器缓存机制
缓存类型:
- 强缓存
javascript
// 响应头设置
Cache-Control: max-age=3600 // 1小时内使用缓存
Expires: Wed, 21 Oct 2026 07:28:00 GMT // 过期时间(HTTP/1.0)
// 优先级:Cache-Control > Expires
Cache-Control常用指令:
max-age=<seconds>:缓存最大有效时间no-cache:使用缓存前必须验证no-store:完全不缓存public:可被任何缓存存储private:只能被浏览器缓存
- 协商缓存
javascript
// 基于Last-Modified
// 1. 首次请求
Response Headers:
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
// 2. 再次请求
Request Headers:
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
Response:
304 Not Modified (使用缓存)
或
200 OK (返回新资源)
// 基于ETag(更精确)
// 1. 首次请求
Response Headers:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// 2. 再次请求
Request Headers:
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Response:
304 Not Modified 或 200 OK
缓存策略实践:
javascript
// 1. HTML文件 - no-cache,每次验证
Cache-Control: no-cache
// 2. CSS/JS带hash - 长期缓存
// app.a1b2c3.js
Cache-Control: max-age=31536000, immutable
// 3. 图片资源 - 适中缓存
Cache-Control: max-age=86400
// 4. API数据 - 不缓存
Cache-Control: no-store
浏览器缓存位置(优先级从高到低):
- Service Worker Cache
- Memory Cache(内存缓存)
- Disk Cache(磁盘缓存)
- Push Cache(HTTP/2推送缓存)
二、HTTP与HTTPS
5. HTTP各版本的区别
HTTP/1.0:
- 无连接:每次请求都要建立新连接
- 无状态:服务器不保存客户端信息
HTTP/1.1:
javascript
// 持久连接
Connection: keep-alive
// 管道化(发送多个请求,按顺序接收响应)
// 请求1 → 请求2 → 请求3 → 响应1 → 响应2 → 响应3
// 新增请求方法
PUT, DELETE, PATCH, OPTIONS
// Host头必需
Host: www.example.com
// 缓存控制增强
Cache-Control: max-age=3600
// 分块传输编码
Transfer-Encoding: chunked
HTTP/2:
javascript
// 1. 二进制分帧
// 将消息分割为更小的帧,提高传输效率
// 2. 多路复用
// 单一连接上并行交错多个请求和响应
// 解决了HTTP/1.1的队头阻塞问题
// 3. 头部压缩(HPACK)
// 减少重复头部传输
// 4. 服务器推送
// 服务器主动推送资源
Link: </style.css>; rel=preload; as=style
// 5. 请求优先级
// 客户端可以指定资源优先级
HTTP/3 (基于QUIC):
- 基于UDP而非TCP
- 解决TCP队头阻塞
- 更快的连接建立(0-RTT)
- 连接迁移(网络切换不断连)
6. HTTPS工作原理
HTTPS = HTTP + SSL/TLS
加密过程:
1. 客户端发起HTTPS请求
↓
2. 服务器返回证书(包含公钥)
↓
3. 客户端验证证书有效性
↓
4. 客户端生成随机密钥,用服务器公钥加密
↓
5. 服务器用私钥解密获得密钥
↓
6. 双方使用该密钥进行对称加密通信
证书验证流程:
javascript
// 1. 检查证书是否过期
Valid From: 2025-01-01
Valid To: 2026-01-01
// 2. 检查域名是否匹配
Subject: CN=www.example.com
// 3. 检查证书颁发机构是否可信
Issuer: CN=DigiCert
// 4. 检查证书是否被吊销
// 通过CRL或OCSP检查
混合加密:
- 非对称加密:交换密钥(RSA、ECC)
- 对称加密:实际数据传输(AES)
TLS握手过程:
客户端 服务器
| |
|-------- ClientHello --------------->|
| (支持的加密套件、随机数) |
| |
|<------- ServerHello ----------------|
| (选择的加密套件、随机数、证书) |
| |
|------ ClientKeyExchange ----------->|
| (预主密钥,用服务器公钥加密) |
| |
|------ ChangeCipherSpec ------------>|
|------ Finished -------------------->|
| |
|<----- ChangeCipherSpec -------------|
|<----- Finished ---------------------|
| |
|======== 加密通信开始 ================|
7. 跨域问题与解决方案
同源策略: 协议 + 域名 + 端口 完全相同
javascript
// 同源示例
http://www.example.com/page1.html
http://www.example.com/page2.html // ✅ 同源
// 不同源示例
http://www.example.com
https://www.example.com // ❌ 协议不同
http://api.example.com // ❌ 域名不同
http://www.example.com:8080 // ❌ 端口不同
解决方案:
1. CORS(跨域资源共享)
javascript
// 服务器端设置
// 简单请求
Access-Control-Allow-Origin: * // 或指定域名
Access-Control-Allow-Credentials: true
// 预检请求(非简单请求)
// OPTIONS请求
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400 // 预检结果缓存时间
// Node.js示例
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
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(200);
}
next();
});
2. JSONP(仅支持GET)
javascript
// 客户端
function handleResponse(data) {
console.log(data);
}
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
// 服务器端返回
handleResponse({ name: 'Alice', age: 18 })
3. 代理服务器
javascript
// 开发环境 - Webpack DevServer
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
};
// 生产环境 - Nginx
location /api {
proxy_pass http://api.example.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
4. PostMessage(跨窗口通信)
javascript
// 父窗口
const iframe = document.getElementById('myIframe');
iframe.contentWindow.postMessage('Hello', 'http://child.com');
window.addEventListener('message', (event) => {
if (event.origin !== 'http://child.com') return;
console.log(event.data);
});
// 子窗口
window.addEventListener('message', (event) => {
if (event.origin !== 'http://parent.com') return;
console.log(event.data);
event.source.postMessage('Hi', event.origin);
});
5. WebSocket
javascript
// WebSocket不受同源策略限制
const ws = new WebSocket('ws://api.example.com');
ws.onopen = () => {
ws.send('Hello Server');
};
ws.onmessage = (event) => {
console.log(event.data);
};
三、浏览器存储
8. Cookie、LocalStorage、SessionStorage对比
| 特性 | Cookie | LocalStorage | SessionStorage |
|---|---|---|---|
| 容量 | 4KB | 5-10MB | 5-10MB |
| 生命周期 | 可设置过期时间 | 永久(除非手动清除) | 页面关闭时清除 |
| 作用域 | 同源且路径匹配 | 同源 | 同源且同窗口 |
| 网络传输 | 每次HTTP请求都携带 | 不参与 | 不参与 |
| 操作API | 复杂 | 简单 | 简单 |
Cookie操作:
javascript
// 1. 设置Cookie
document.cookie = 'username=Alice; max-age=3600; path=/; secure; samesite=strict';
// Cookie属性
// expires: 过期时间(UTC格式)
// max-age: 有效期(秒)
// domain: 域名
// path: 路径
// secure: 仅HTTPS传输
// httpOnly: 禁止JavaScript访问(仅服务器端设置)
// samesite: 防止CSRF攻击
// - strict: 完全禁止第三方Cookie
// - lax: GET请求可以携带
// - none: 无限制(需配合secure使用)
// 2. 读取Cookie
function getCookie(name) {
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const [key, value] = cookie.split('=');
if (key === name) {
return decodeURIComponent(value);
}
}
return null;
}
// 3. 删除Cookie
document.cookie = 'username=; max-age=0';
// 4. 封装Cookie工具类
class CookieUtil {
static set(name, value, options = {}) {
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (options.maxAge) cookie += `; max-age=${options.maxAge}`;
if (options.path) cookie += `; path=${options.path}`;
if (options.domain) cookie += `; domain=${options.domain}`;
if (options.secure) cookie += '; secure';
if (options.sameSite) cookie += `; samesite=${options.sameSite}`;
document.cookie = cookie;
}
static get(name) {
const cookies = document.cookie.split('; ');
for (const cookie of cookies) {
const [key, value] = cookie.split('=');
if (decodeURIComponent(key) === name) {
return decodeURIComponent(value);
}
}
return null;
}
static remove(name, options = {}) {
this.set(name, '', { ...options, maxAge: 0 });
}
}
LocalStorage操作:
javascript
// 1. 存储
localStorage.setItem('user', JSON.stringify({ name: 'Alice', age: 18 }));
// 2. 读取
const user = JSON.parse(localStorage.getItem('user'));
// 3. 删除
localStorage.removeItem('user');
// 4. 清空
localStorage.clear();
// 5. 遍历
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}
// 6. 封装LocalStorage工具类
class StorageUtil {
static set(key, value, expire = null) {
const data = {
value,
expire: expire ? Date.now() + expire : null
};
localStorage.setItem(key, JSON.stringify(data));
}
static get(key) {
const item = localStorage.getItem(key);
if (!item) return null;
const data = JSON.parse(item);
// 检查是否过期
if (data.expire && Date.now() > data.expire) {
localStorage.removeItem(key);
return null;
}
return data.value;
}
static remove(key) {
localStorage.removeItem(key);
}
static clear() {
localStorage.clear();
}
}
// 使用示例
StorageUtil.set('token', 'abc123', 3600000); // 1小时后过期
SessionStorage操作:
javascript
// API与LocalStorage相同
sessionStorage.setItem('temp', 'data');
sessionStorage.getItem('temp');
sessionStorage.removeItem('temp');
sessionStorage.clear();
Storage事件监听:
javascript
// 监听其他标签页的storage变化
window.addEventListener('storage', (event) => {
console.log('Key:', event.key);
console.log('Old Value:', event.oldValue);
console.log('New Value:', event.newValue);
console.log('URL:', event.url);
});
9. IndexedDB
特点:
- 大容量存储(通常大于250MB)
- 支持事务
- 异步操作
- 可以存储复杂数据类型
javascript
// 1. 打开数据库
const request = indexedDB.open('MyDatabase', 1);
request.onerror = () => console.error('数据库打开失败');
request.onsuccess = (event) => {
const db = event.target.result;
console.log('数据库打开成功');
};
// 2. 创建对象存储空间(表)
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('users')) {
const objectStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });
}
};
// 3. 添加数据
function addUser(db, user) {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
const request = objectStore.add(user);
request.onsuccess = () => console.log('用户添加成功');
request.onerror = () => console.error('用户添加失败');
}
// 4. 读取数据
function getUser(db, id) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const request = objectStore.get(id);
request.onsuccess = (event) => {
console.log('User:', event.target.result);
};
}
// 5. 更新数据
function updateUser(db, user) {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
const request = objectStore.put(user);
request.onsuccess = () => console.log('用户更新成功');
}
// 6. 删除数据
function deleteUser(db, id) {
const transaction = db.transaction(['users'], 'readwrite');
const objectStore = transaction.objectStore('users');
const request = objectStore.delete(id);
request.onsuccess = () => console.log('用户删除成功');
}
// 7. 使用索引查询
function getUserByEmail(db, email) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const index = objectStore.index('email');
const request = index.get(email);
request.onsuccess = (event) => {
console.log('User:', event.target.result);
};
}
// 8. 游标遍历
function getAllUsers(db) {
const transaction = db.transaction(['users'], 'readonly');
const objectStore = transaction.objectStore('users');
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log('User:', cursor.value);
cursor.continue();
}
};
}