前端综合实战:跨域通信、离线存储与 URL 到页面全流程

文章目录

  • 前言
  • 一、同源策略与跨域
    • [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))
  }
}

七、易混淆点

  1. 同源策略限制 :限制 AJAX 跨域请求,但不限制 <script><img><link> 等标签的跨域加载。
  2. SSE vs WebSocket:SSE 是单向(服务端 → 客户端),WebSocket 是双向。SSE 基于 HTTP,WebSocket 是独立协议。
  3. localStorage vs sessionStorage:localStorage 永久存储(需手动清除),sessionStorage 会话结束自动清除。
  4. Cookie vs Token:Cookie 自动携带,Token 需手动设置请求头。Cookie 有大小限制,Token 无限制。

八、思考与练习

1. 同源策略限制了什么?不限制什么?

解析:

  • 限制:AJAX 跨域请求、Cookie 读取、DOM 访问
  • 不限制<script><img><link><video> 等标签的跨域加载

2. 跨标签页通信有哪些方案?

解析:

  • BroadcastChannel:同源标签页专用
  • localStorage + storage 事件:简单数据同步
  • SharedWorker:复杂逻辑共享
  • postMessage:跨域窗口通信

3. 从输入 URL 到页面显示经历了哪些步骤?

解析:

  1. URL 解析
  2. DNS 解析
  3. TCP 三次握手(HTTPS 还需 TLS 握手)
  4. HTTP 请求/响应
  5. HTML 解析 → DOM 树
  6. CSS 解析 → CSSOM 树
  7. 合并 → 渲染树
  8. 布局 → 绘制 → 合成

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 → 解析 → 渲染