22 · Web 平台能力(浏览器新特性)
本篇解决什么问题: 很多"高级需求"其实浏览器已经内置了,但很多人还在引第三方库。 本篇梳理 Web Components / WebAssembly / Web Workers / Service Worker·PWA / WebRTC / WebGPU, 重点说清什么时候该用、什么时候别碰——以及各自的坑。
📌 原则:平台能力优先。能用原生的就别引库,但要认清原生能力的边界。
开篇速览
| 能力 | 解决什么 | 成熟可用度 | 典型场景 |
|---|---|---|---|
| Web Components | 框架无关的组件复用 | ✅ 成熟(但生态弱) | 跨框架组件库、微前端组件共享 |
| WebAssembly | 让 C/C++/Rust 在浏览器跑 | ✅ 成熟 | 音视频编解码、图像/3D、移植 C++ 库 |
| Web Workers | 把计算挪出主线程 | ✅ 成熟 | 大数据处理、加密、解析 |
| Service Worker / PWA | 离线、缓存、安装到桌面 | ✅ 成熟(有坑) | 离线可用、消息推送 |
| WebRTC | 浏览器间实时音视频/数据 | ✅ 成熟(复杂) | 视频会议、屏幕共享、P2P |
| WebGPU | 现代 GPU 编程接口 | ⚠️ 逐步普及 | 高性能 3D、端侧推理 |
一、Web Components:原生组件标准
三块基石:
| 技术 | 作用 |
|---|---|
| Custom Elements | 自定义标签 + 生命周期(connectedCallback 等) |
| Shadow DOM | 样式与结构隔离,不泄漏到外部 |
HTML Template(<template> / <slot>) | 声明式模板与内容分发 |
class MyCard extends HTMLElement {
constructor() { super(); this.attachShadow({ mode: 'open' }) }
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>.card { border:1px solid #ddd; padding:12px }</style>
<div class="card"><slot name="title"></slot><slot></slot></div>`
}
disconnectedCallback() { /* 清理,防泄漏 */ }
}
customElements.define('my-card', MyCard)什么时候用 / 别用:
| ✅ 用 | ❌ 别用 |
|---|---|
| 要在 React / Vue / Angular 共用同一套组件 | 只在一个框架内用(框架自己的组件更好用) |
| 微前端里共享基础组件(见 A24) | 需要复杂状态管理与数据驱动渲染 |
| 做 SDK / 第三方嵌入组件 | 需要 SSR(Shadow DOM 的 SSR 很别扭) |
⚠️ 痛点:没有数据绑定与响应式、表单集成麻烦、SSR 支持差。 所以 Web Components 更适合"基础展示型组件",不适合承载业务逻辑。
二、WebAssembly(Wasm):让重计算跑满速
是什么:一种二进制指令格式,作为 JS 的补充,在浏览器里以接近原生速度运行。
不是什么:❌ 不是 JS 的替代品,❌ 不能直接操作 DOM(要通过 JS 胶水层)。
// 加载并调用 Wasm
const { instance } = await WebAssembly.instantiateStreaming(fetch('/add.wasm'))
instance.exports.add(1, 2) // 3适合:
| 场景 | 例子 |
|---|---|
| 计算密集 | 图像/视频编解码(FFmpeg.wasm)、CAD、物理引擎 |
| 移植已有 C/C++/Rust 库 | 把成熟库搬到前端,避免重写 |
| 游戏引擎 | Unity / Unreal 导出到 Web |
不适合:DOM 操作多的场景(跨 JS/Wasm 边界的调用有开销)、普通 CRUD。
💡 判断标准:计算时间 > 调用边界开销才划算。小任务用 Wasm 反而更慢。
三、Web Workers:把重活挪出主线程
主线程要负责渲染与交互(见 A09 的 16.67ms 帧预算), 长任务会卡住 UI。Worker 提供独立线程。
// main.js
const worker = new Worker('/worker.js', { type: 'module' })
worker.postMessage({ cmd: 'parse', data: bigData })
worker.onmessage = e => console.log('结果', e.data)
worker.onerror = e => console.error(e)
worker.terminate() // ✅ 用完必须销毁
// worker.js
self.onmessage = e => {
const result = heavyCompute(e.data)
self.postMessage(result) // 结构化克隆(可传 Transferable 零拷贝)
}关键限制(面试常考):
| 限制 | 说明 |
|---|---|
| 无 DOM | 不能碰 document、window(只有 self) |
| 独立作用域 | 与主线程的变量不共享 |
通信靠 postMessage | 默认结构化克隆(有拷贝开销) |
无 localStorage | 可用 IndexedDB |
零拷贝技巧:传 Transferable Objects(ArrayBuffer),避免大数组复制:
worker.postMessage(buffer, [buffer]) // 第二个参数是"转移"列表💡 Comlink 这类库把
postMessage包装成 Promise 调用,开发体验接近本地函数。
四、Service Worker 与 PWA:离线与安装
Service Worker 是运行在浏览器后台的代理,能拦截网络请求。
页面 ──请求──► Service Worker ──► 命中缓存?直接返回 : 走网络并写入缓存// sw.js —— 缓存优先(Cache First)
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request).then(hit => hit || fetch(e.request).then(res => {
const copy = res.clone()
caches.open('v1').then(c => c.put(e.request, copy))
return res
}))
)
})PWA 三要素:HTTPS + Web App Manifest + Service Worker。
最大的坑(务必记住):
| 坑 | 说明 |
|---|---|
| 更新滞后 | 旧 SW 会一直控制页面,新 SW 要等所有页面关闭才激活 → 用 skipWaiting() + clients.claim(),或提示用户刷新 |
| 缓存脏数据 | 缓存策略写错会导致用户永远看到旧版本 → 必须做版本化缓存名 + 清理旧缓存 |
| 调试困难 | 只在生产表现、DevTools 里要勾 "Update on reload" |
⚠️ 本项目的前车之鉴:前端缓存失效是发布事故的重灾区,详见 A16 · CI/CD 与部署运维。
五、WebRTC:浏览器间的实时通信
能力:浏览器**点对点(P2P)**传输音视频与任意数据,无需中转服务器(但实际大多需要)。
三个核心 API:
| API | 作用 |
|---|---|
getUserMedia() | 拿到摄像头 / 麦克风 / 屏幕 |
RTCPeerConnection | 建立 P2P 连接,传输音视频与数据通道 |
RTCDataChannel | 传任意数据(低延迟,类似 WebSocket 但 P2P) |
必须知道的现实:
- NAT 穿透需要 STUN(拿公网地址)+ TURN(中继兜底,要花钱)
- 多人会议通常用 SFU(选择性转发单元)架构,纯 P2P 只适合 2~4 人
- 信令(SDP 交换)协议不在 WebRTC 标准里,要自己用 WebSocket 实现
六、WebGPU:下一代图形接口
定位:WebGL 的继任者,暴露现代 GPU 能力(类似 Vulkan / Metal / D3D12)。
| 对比 | WebGL | WebGPU |
|---|---|---|
| 抽象层次 | 高(OpenGL ES 风格) | 低(显式命令/管线) |
| 计算着色器 | ❌ | ✅ Compute Shader |
| 端侧 ML 推理 | 困难 | 现实可行 |
| 浏览器支持 | 全面 | ⚠️ 主流浏览器已支持,但需确认目标用户环境 |
💡 对前端的意义:WebGPU 让端侧模型推理(配合 Wasm)成为现实, 是 AI 应用下沉到浏览器的重要前提(见 AI 领域文档)。
自检清单
- [ ] 能说出 Web Components 三块基石,以及它"适合/不适合"的场景
- [ ] 能判断什么任务该用 WebAssembly(计算 > 边界开销)
- [ ] 能说清 Web Worker 的三个限制,以及 Transferable 零拷贝的作用
- [ ] 能解释 Service Worker 更新滞后与缓存脏数据两个坑
- [ ] 能说出 WebRTC 为什么"P2P 但仍需服务器"(STUN/TURN/信令)
常见误区
| ❌ 误区 | ✅ 事实 |
|---|---|
| "WebAssembly 会取代 JS" | 它是补充,DOM 操作仍要 JS 胶水层 |
| "Worker 里能操作 DOM" | 不能,只能通过 postMessage 通信 |
| "PWA = 加个 manifest" | 核心是 Service Worker,且更新与缓存策略最难 |
| "WebRTC 不需要服务器" | 需要 STUN/TURN + 信令服务器 |
动手练习
- 写一个 Worker,把 10 万条数据的排序挪进去,用 Performance 面板对比主线程前后的长任务时长。
- 用
Transferable传一个 50MB 的ArrayBuffer,验证零拷贝(对比普通postMessage的耗时)。 - 给本站(VitePress)加一个最简单的 Service Worker,体会"更新滞后"问题,再用
skipWaiting修复。
相关文档
- 主线程与 16.67ms 帧预算 → A04 · 浏览器与网络 · A09 · JavaScript 运行时
- 微前端里的组件共享 → A24 · 微前端架构落地
- 发布侧的缓存失效 → A16 · CI/CD 与部署运维
- 端侧 AI 推理的背景 → AI 05 · 工程化与 LLMOps