文章目录
- 前言
- 一、同源策略与跨域
-
- [1.1 同源定义](#1.1 同源定义)
- [1.2 跨域解决方案](#1.2 跨域解决方案)
- 二、跨标签页通信
-
- [2.1 BroadcastChannel](#2.1 BroadcastChannel)
- [2.2 localStorage 事件](#2.2 localStorage 事件)
- [2.3 SharedWorker](#2.3 SharedWorker)
- [2.4 方案对比](#2.4 方案对比)
- [三、SSE vs WebSocket](#三、SSE vs WebSocket)
-
- [3.1 SSE(Server-Sent Events)](#3.1 SSE(Server-Sent Events))
- [3.2 WebSocket](#3.2 WebSocket)
- [3.3 区别](#3.3 区别)
- 四、离线存储方案选型
-
- [4.1 方案对比](#4.1 方案对比)
- [4.2 选型指南](#4.2 选型指南)
- [五、「从输入 URL 到页面显示」](#五、「从输入 URL 到页面显示」)
-
- [5.1 完整流程](#5.1 完整流程)
- [5.2 面试回答要点](#5.2 面试回答要点)
- 六、高频手写题汇总
-
- [6.1 防抖](#6.1 防抖)
- [6.2 节流](#6.2 节流)
- [6.3 深拷贝](#6.3 深拷贝)
- [6.4 Promise.all](#6.4 Promise.all)
- [6.5 发布订阅](#6.5 发布订阅)
- 七、易混淆点
- 八、思考与练习
- 总结
前言
本篇是系列文章的收尾,汇总面试高频手写题和综合性问题,帮助系统性复习。内容涵盖:
- 同源策略与跨域
- 跨标签页通信
- SSE vs WebSocket
- 离线存储方案选型
- 「从输入 URL 到页面显示」串联题
一、同源策略与跨域
1.1 同源定义
javascript
// 同源:协议、域名、端口完全相同
https://example.com:443/path
↓ ↓ ↓
协议 域名 端口
// 不同源的情况:
// 协议不同:http vs https
// 域名不同:example.com vs api.example.com
// 端口不同::80 vs :8080
1.2 跨域解决方案
javascript
// 1. CORS(最常用)
// 服务端设置响应头
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true // 允许携带 Cookie
// 2. 代理
// 开发环境:devServer proxy
devServer: {
proxy: {
'/api': 'http://localhost:8080'
}
}
// 生产环境:Nginx 反向代理
location /api {
proxy_pass http://backend:8080;
}
// 3. JSONP(只支持 GET)
function jsonp(url, callback) {
const script = document.createElement('script')
script.src = `${url}?callback=${callback}`
document.body.appendChild(script)
}
// 4. postMessage(跨窗口通信)
otherWindow.postMessage(data, targetOrigin)
二、跨标签页通信
2.1 BroadcastChannel
javascript
// 标签页 A
const channel = new BroadcastChannel('my-channel')
channel.postMessage({ type: 'update', data: 'Hello' })
// 标签页 B
const channel = new BroadcastChannel('my-channel')
channel.onmessage = (event) => {
console.log(event.data) // { type: 'update', data: 'Hello' }
}
2.2 localStorage 事件
javascript
// 标签页 A
localStorage.setItem('message', JSON.stringify({ text: 'Hello' }))
// 标签页 B
window.addEventListener('storage', (event) => {
if (event.key === 'message') {
console.log(JSON.parse(event.newValue))
}
})
2.3 SharedWorker
javascript
// worker.js
const ports = []
onconnect = (e) => {
const port = e.ports[0]
ports.push(port)
port.onmessage = (event) => {
// 广播给其他标签页
ports.forEach(p => {
if (p !== port) p.postMessage(event.data)
})
}
}
// 标签页
const worker = new SharedWorker('worker.js')
worker.port.onmessage = (e) => console.log(e.data)
worker.port.postMessage('Hello from tab A')
2.4 方案对比
| 方案 | 适用场景 | 兼容性 |
|---|---|---|
| BroadcastChannel | 同源标签页通信 | 现代浏览器 |
| localStorage | 简单数据同步 | 全兼容 |
| SharedWorker | 复杂逻辑共享 | 现代浏览器 |
| postMessage | 跨域窗口通信 | 全兼容 |
三、SSE vs WebSocket
3.1 SSE(Server-Sent Events)
javascript
// 客户端
const source = new EventSource('/api/events')
source.onmessage = (event) => {
console.log(event.data)
}
source.addEventListener('custom', (event) => {
console.log(event.data)
})
source.onerror = (event) => {
console.log('连接错误')
}
// 服务端(Node.js)
app.get('/api/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
setInterval(() => {
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`)
}, 1000)
})
3.2 WebSocket
javascript
// 客户端
const ws = new WebSocket('ws://localhost:8080')
ws.onopen = () => {
ws.send('Hello Server')
}
ws.onmessage = (event) => {
console.log(event.data)
}
ws.onclose = () => {
console.log('连接关闭')
}
// 服务端(Node.js + ws)
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })
wss.on('connection', (ws) => {
ws.on('message', (message) => {
ws.send(`Echo: ${message}`)
})
})
3.3 区别
| 对比项 | SSE | WebSocket |
|---|---|---|
| 方向 | 服务端 → 客户端(单向) | 双向通信 |
| 协议 | HTTP | 独立协议(ws://) |
| 数据格式 | 文本 | 文本 + 二进制 |
| 自动重连 | 支持 | 需手动实现 |
| 适用场景 | 通知、实时数据推送 | 聊天、游戏、协作编辑 |
四、离线存储方案选型
4.1 方案对比
| 方案 | 容量 | 过期机制 | 适用场景 |
|---|---|---|---|
| Cookie | 4KB | 设置过期时间 | 认证状态、会话管理 |
| localStorage | 5-10MB | 永久(需手动清除) | 用户偏好、缓存数据 |
| sessionStorage | 5-10MB | 会话结束 | 表单暂存、页面状态 |
| IndexedDB | 无限制 | 无 | 大量结构化数据、离线应用 |
| Cache API | 无限制 | 手动管理 | 资源缓存、Service Worker |
4.2 选型指南
javascript
// 1. 小量简单数据 → Cookie
document.cookie = 'session=abc; max-age=3600; secure; httponly'
// 2. 中量键值对 → localStorage
localStorage.setItem('theme', 'dark')
localStorage.setItem('lang', 'zh-CN')
// 3. 会话级数据 → sessionStorage
sessionStorage.setItem('formData', JSON.stringify(formData))
// 4. 大量结构化数据 → IndexedDB
const request = indexedDB.open('myDB', 1)
request.onupgradeneeded = (event) => {
const db = event.target.result
db.createObjectStore('users', { keyPath: 'id' })
}
// 5. 资源缓存 → Cache API + Service Worker
caches.open('v1').then(cache => {
cache.addAll(['/index.html', '/styles.css', '/app.js'])
})
五、「从输入 URL 到页面显示」
5.1 完整流程
1. URL 解析
- 浏览器解析 URL(协议、域名、端口、路径、参数)
2. DNS 解析
- 浏览器缓存 → 系统缓存 → 路由器 → ISP → 根域名服务器
- 递归查询,获取 IP 地址
3. 建立 TCP 连接
- 三次握手:SYN → SYN+ACK → ACK
- HTTPS 还需要 TLS 握手
4. 发送 HTTP 请求
- 请求行:方法、路径、协议版本
- 请求头:Host、Cookie、Content-Type 等
- 请求体:POST 数据
5. 服务器处理
- 负载均衡 → Web 服务器 → 应用服务器 → 数据库
- 返回 HTTP 响应
6. 浏览器渲染
- 解析 HTML → 构建 DOM 树
- 解析 CSS → 构建 CSSOM 树
- 合并 DOM + CSSOM → 渲染树
- 布局(Layout)→ 绘制(Paint)→ 合成(Composite)
7. JavaScript 执行
- 下载、解析、执行 JS
- 可能修改 DOM/CSSOM,触发重排/重绘
5.2 面试回答要点
javascript
// 回答结构:
// 1. 网络阶段
// - DNS 解析(迭代/递归查询)
// - TCP 三次握手
// - TLS 握手(HTTPS)
// - HTTP 请求/响应
// 2. 解析阶段
// - HTML 解析 → DOM 树
// - CSS 解析 → CSSOM 树
// - 合并 → 渲染树
// 3. 渲染阶段
// - 布局(计算位置大小)
// - 绘制(生成像素)
// - 合成(GPU 合成图层)
// 4. 优化点
// - DNS 预解析:<link rel="dns-prefetch">
// - 预连接:<link rel="preconnect">
// - 资源提示:preload、prefetch
// - 关键渲染路径优化
六、高频手写题汇总
6.1 防抖
javascript
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
6.2 节流
javascript
function throttle(fn, interval) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= interval) {
lastTime = now
fn.apply(this, args)
}
}
}
6.3 深拷贝
javascript
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj)
const result = Array.isArray(obj) ? [] : {}
map.set(obj, result)
for (const key of Reflect.ownKeys(obj)) {
result[key] = deepClone(obj[key], map)
}
return result
}
6.4 Promise.all
javascript
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(value) => {
results[index] = value
if (++count === promises.length) resolve(results)
},
reject
)
})
if (promises.length === 0) resolve(results)
})
}
6.5 发布订阅
javascript
class EventEmitter {
events = {}
on(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
return () => this.off(event, callback)
}
off(event, callback) {
if (!this.events[event]) return
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
emit(event, ...args) {
if (!this.events[event]) return
this.events[event].forEach(cb => cb(...args))
}
}
七、易混淆点
- 同源策略限制 :限制 AJAX 跨域请求,但不限制
<script>、<img>、<link>等标签的跨域加载。 - SSE vs WebSocket:SSE 是单向(服务端 → 客户端),WebSocket 是双向。SSE 基于 HTTP,WebSocket 是独立协议。
- localStorage vs sessionStorage:localStorage 永久存储(需手动清除),sessionStorage 会话结束自动清除。
- Cookie vs Token:Cookie 自动携带,Token 需手动设置请求头。Cookie 有大小限制,Token 无限制。
八、思考与练习
1. 同源策略限制了什么?不限制什么?
解析:
- 限制:AJAX 跨域请求、Cookie 读取、DOM 访问
- 不限制 :
<script>、<img>、<link>、<video>等标签的跨域加载
2. 跨标签页通信有哪些方案?
解析:
- BroadcastChannel:同源标签页专用
- localStorage + storage 事件:简单数据同步
- SharedWorker:复杂逻辑共享
- postMessage:跨域窗口通信
3. 从输入 URL 到页面显示经历了哪些步骤?
解析:
- URL 解析
- DNS 解析
- TCP 三次握手(HTTPS 还需 TLS 握手)
- HTTP 请求/响应
- HTML 解析 → DOM 树
- CSS 解析 → CSSOM 树
- 合并 → 渲染树
- 布局 → 绘制 → 合成
4. localStorage 和 sessionStorage 的区别?
解析:
- localStorage:永久存储,需手动清除,同源所有标签页共享
- sessionStorage:会话级存储,标签页关闭自动清除,仅当前标签页可用
5. SSE 和 WebSocket 的区别?
解析:
- SSE:单向(服务端 → 客户端),基于 HTTP,自动重连,适用于通知、实时数据推送
- WebSocket:双向通信,独立协议,需手动重连,适用于聊天、游戏、协作编辑
总结
- 同源策略:协议、域名、端口相同,限制 AJAX 跨域
- 跨域方案:CORS(最常用)、代理、JSONP、postMessage
- 跨标签通信:BroadcastChannel、localStorage、SharedWorker、postMessage
- SSE vs WebSocket:前者单向基于 HTTP,后者双向独立协议
- 离线存储:Cookie(4KB)、localStorage/sessionStorage(5-10MB)、IndexedDB(无限制)
- URL 到页面:DNS → TCP → HTTP → 解析 → 渲染