HTML5 高级 API:Web Storage、Worker、History 与更多

2024-08-24 前端

前言

HTML5 不仅仅是一个标记语言的版本更新,它带来了一整套强大的浏览器端 API,彻底改变了 Web 应用的能力边界。在 HTML5 之前,浏览器只是一个文档展示器;而在 HTML5 之后,浏览器变成了一个完整的应用平台。

本文将深入剖析七个核心 HTML5 高级 API,每个知识点都配有可直接运行的完整代码示例。无论你是前端新人还是资深开发者,这篇文章都能帮你系统性地掌握这些 API 的原理和使用技巧。


1. Web Storage(localStorage / sessionStorage)

1.1 为什么需要 Web Storage?

在 Web Storage 出现之前,前端存储数据只能依赖 Cookie。Cookie 的设计初衷是服务端会话管理,而不是客户端数据持久化,因此存在诸多限制。

特性 Cookie Web Storage
容量 4KB 左右 5MB+(各浏览器略有差异)
作用域 同站点下所有路径共享 同源策略(协议+域名+端口)
过期策略 可设置过期时间 仅 localStorage 持久化,sessionStorage 随标签页关闭而清除
网络请求 每次 HTTP 请求自动携带 不会自动发送到服务端
操作接口 繁琐的 document.cookie 字符串操作 简洁的键值对 API

1.2 localStorage 与 sessionStorage 的区别

两者的 API 完全一致,唯一的区别是数据生命周期:

  • localStorage:数据持久化存储,除非用户手动清除或通过代码删除,否则永久保留。
  • sessionStorage:数据仅在当前浏览器标签页(Tab)会话期间有效,关闭标签页即清除。同一个站点的不同标签页之间不共享。

1.3 基本操作

下面是一个完整的交互示例:

1.4 storage 事件:跨标签页通信

当同一个源下的其他标签页修改了 localStorage 时,当前页面会触发 storage 事件。注意:修改数据的页面本身不会触发此事件。

StorageEvent 对象包含以下属性:

  • key:被修改的键名
  • oldValue:修改前的值
  • newValue:修改后的值
  • url:触发修改的页面 URL
  • storageArea:被修改的 Storage 对象

1.5 存储限制与异常处理

浏览器为每个源分配了约 5MB 的存储空间。当超出限制时,setItem() 会抛出 QuotaExceededError:

function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, value);
    return true;
  } catch (e) {
    if (e.name === 'QuotaExceededError' || e.code === 22) {
      console.error('存储空间已满,请清理后重试');
      // 可以在这里触发清理策略,例如删除最旧的数据
      return false;
    }
    console.error('存储出错:', e);
    return false;
  }
}

// 检查当前使用量
function getStorageUsage() {
  let total = 0;
  for (let key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      total += key.length + localStorage[key].length;
    }
  }
  // 返回 KB,每个字符约 2 字节(UTF-16)
  return (total * 2 / 1024).toFixed(2) + ' KB';
}

1.6 封装带过期时间的 Storage 工具类

原生 Web Storage 不支持过期时间,我们可以封装一个工具类:


2. Web Worker

2.1 为什么需要 Web Worker?

JavaScript 是单线程语言,所有的 UI 渲染、事件处理、脚本执行都在同一个主线程中。当执行耗时的计算任务时,页面会"卡住"——用户无法点击、滚动或输入。

Web Worker 允许我们在后台线程中运行 JavaScript,不阻塞主线程,从而保持页面的流畅响应。

2.2 主线程与 Worker 通信

通信基于消息传递模型:

// main.js(主线程)
const worker = new Worker('worker.js');

// 向 Worker 发送消息
worker.postMessage({ type: 'start', payload: 42 });

// 接收 Worker 的消息
worker.onmessage = function (event) {
  console.log('从 Worker 收到:', event.data);
};

// 错误处理
worker.onerror = function (event) {
  console.error('Worker 错误:', event.message);
};

// 终止 Worker
// worker.terminate();
// worker.js(Worker 线程)
// 接收主线程消息
self.onmessage = function (event) {
  const data = event.data;
  console.log('Worker 收到:', data);

  // 计算结果
  const result = heavyComputation(data.payload);

  // 发送结果回主线程
  self.postMessage({ status: 'done', result: result });
};

function heavyComputation(n) {
  let sum = 0;
  for (let i = 0; i < n * 10000000; i++) {
    sum += Math.sqrt(i);
  }
  return sum;
}

2.3 计算密集型任务示例:大数计算

下面是一个完整的可运行示例,对比使用 Worker 和不使用 Worker 时的页面响应差异:

2.4 Worker 可用的 API 与限制

Worker 线程拥有独立的全局上下文(self 或 DedicatedWorkerGlobalScope),不能直接访问:

不可用 可用
DOM(document、window、parent) self / this
alert()、confirm() postMessage()
console 部分受限 console.log()(Worker 支持)
大部分 DOM API fetch() / XMLHttpRequest
setTimeout / setInterval
importScripts()(加载外部脚本)
WebAssembly
Navigator 某些属性
FileReader / Blob

2.5 SharedWorker:跨标签页共享

SharedWorker 允许多个同源标签页共享同一个 Worker 实例,非常适合做跨标签页的状态同步:

// shared-worker.js
const connections = [];

self.onconnect = function (event) {
  const port = event.ports[0];
  connections.push(port);

  port.onmessage = function (e) {
    // 广播消息给所有连接的标签页
    connections.forEach(function (conn) {
      conn.postMessage(e.data);
    });
  };

  port.postMessage('已连接到 SharedWorker');
};
<!DOCTYPE html>
<html lang="zh-CN">
<body>
  <h1>SharedWorker 演示</h1>
  <p>打开多个标签页,消息会广播到所有页面</p>
  <input type="text" id="msg" placeholder="输入消息" />
  <button onclick="broadcast()">广播</button>
  <ul id="log"></ul>
  <script>
    if (typeof SharedWorker !== 'undefined') {
      const worker = new SharedWorker('shared-worker.js');

      worker.port.onmessage = function (e) {
        const li = document.createElement('li');
        li.textContent = e.data;
        document.getElementById('log').appendChild(li);
      };

      function broadcast() {
        const msg = document.getElementById('msg').value;
        worker.port.postMessage(msg);
      }

      worker.port.start();
    } else {
      document.body.innerHTML = '<p style="color:red">当前浏览器不支持 SharedWorker</p>';
    }
  </script>
</body>
</html>

2.6 Service Worker 简介

Service Worker 是一种特殊的 Web Worker,充当浏览器与网络之间的代理服务器。它不能直接操作 DOM,但它可以:

  • 拦截网络请求(fetch 事件)
  • 管理缓存(Cache API)
  • 离线支持(Service Worker + Cache 实现 PWA)
  • 消息推送(Push API)

注册 Service Worker:需要在主页面中通过 navigator.serviceWorker.register('/service-worker.js') 注册,且文件必须与页面同源。

2.7 性能对比:有无 Worker 的页面响应差异

以下是两种场景下的关键指标对比(以 5 亿次迭代计算为例):

指标 无 Worker 有 Worker
计算耗时 ~3.2 秒 ~3.5 秒(略有额外开销)
页面响应 ❌ 完全卡死 3 秒 ✅ 始终流畅 60fps
用户可交互 ❌ 不能 ✅ 可以
可终止运算 ❌ 不能 ✅ worker.terminate()

结论:有 Worker 的场景虽然计算耗时略长(消息传递有开销),但用户体验天壤之别。


3. History API

3.1 为什么需要 History API?

传统 Web 应用中,每次页面跳转都会导致浏览器重新加载整个页面。SPA(单页应用)通过 JavaScript 动态更新页面内容,但 URL 无法同步更新,导致:

  • 无法使用浏览器的前进/后退按钮
  • 无法为特定状态生成书签或分享链接

History API 解决了这个问题,它允许我们在不刷新页面的情况下修改 URL。

3.2 pushState、replaceState 与 popstate

核心方法:

// pushState: 添加一条新的历史记录
history.pushState(stateObj, title, url);

// replaceState: 替换当前历史记录
history.replaceState(stateObj, title, url);

// popstate 事件: 当用户点击前进/后退时触发
window.addEventListener('popstate', function (event) {
  console.log('状态变化:', event.state);
});

参数说明:

  • stateObj:与记录关联的状态对象(任意 JSON 序列化数据),最大 640KB
  • title:标题参数(大多数浏览器忽略该参数)
  • url:新的 URL(必须同源,可选,默认当前 URL)

3.3 SPA 路由实现原理

下面是一个完整的 SPA 路由实现:

3.4 History API 与 hash 路由的对比

特性 History API Hash 路由
URL 样式 /about(美观) /#/about(带 #)
服务端支持 需要,否则 404 不需要
SEO 更友好 较差
兼容性 IE10+ IE8+
状态数据 支持携带任意数据 不支持

注意:使用 History API 时,服务端必须把所有路由都指向同一个入口文件(如 index.html),否则用户直接访问 /about 或刷新页面时会得到 404 错误。这是 SPA 部署中常见的问题。


4. Drag & Drop API

4.1 基本概念

HTML5 的拖放 API 让元素可以在页面内或页面之间被拖拽。核心是拖拽源(被拖拽的元素)和放置目标(接受拖拽的区域)。

4.2 事件流程

一个完整的拖放操作会依次触发以下事件:

  1. 拖拽源:dragstart → drag → dragend
  2. 放置目标:dragenter → dragover → dragleave / drop

4.3 基本拖拽示例

4.4 DataTransfer 对象详解

dataTransfer 是拖放 API 的核心对象,提供以下重要属性和方法:

属性/方法 说明
setData(format, data) 设置拖拽数据
getData(format) 获取拖拽数据
clearData([format]) 清除拖拽数据
setDragImage(img, x, y) 自定义拖拽时的预览图
effectAllowed 拖拽效果(move/copy/link/none)
dropEffect 放置效果
files 拖拽的文件列表
types 存储的数据格式列表

4.5 拖拽文件上传实现

这是拖放 API 最实用的场景之一:


5. Geolocation API

5.1 基本用法

Geolocation API 允许 Web 应用获取用户的地理位置信息。接口定义在 navigator.geolocation 对象上。

5.2 权限处理与优雅降级

在实际应用中,必须处理用户拒绝授权的情况。最佳实践是渐进增强 + 优雅降级:


6. WebSocket

6.1 为什么需要 WebSocket?

在 WebSocket 出现之前,浏览器与服务端实现实时通信主要靠HTTP 长轮询:

客户端:  "有新消息吗?"
服务端:  "没有。"  ← 等待几秒
客户端:  "有新消息吗?"
服务端:  "没有。"  ← 等待几秒
...
客户端:  "有新消息吗?"
服务端:  "有!消息内容..."

长轮询的问题:

  • 高延迟:消息到达客户端至少需要一次完整的 HTTP 往返
  • 资源浪费:大量重复的 HTTP 请求头
  • 服务器压力大:需要维持大量长连接

WebSocket 通过一次握手建立双向持久连接,彻底解决了这些问题:

客户端:  "连接我吧!"  ← HTTP 升级握手
服务端:  "同意升级!"  ← 101 Switching Protocols
          此后双方随时可以互发消息
服务端:  "有新消息!"  ← 即时推送
客户端:  "收到了!"    ← 即时响应

6.2 WebSocket 完整示例

以下是一个完整的聊天客户端示例(配合 WebSocket 测试服务器):

6.3 WebSocket 与 HTTP 长轮询对比

维度 HTTP 长轮询 WebSocket
连接模式 半双工(交替收发) 全双工(同时收发)
协议开销 每次请求都带完整 HTTP 头 一次握手后仅传输数据帧(2-14 字节头)
延迟 至少一个 RTT 接近于 0 额外延迟
服务端资源 需要维持大量连接 少量连接即可
跨域 服务端控制 同源策略 + 可配置
适用场景 低频轮询 高频实时通信

6.4 WebSocket 对象生命周期

new WebSocket(url)
  └── readyState = CONNECTING (0)
       └── onopen 事件
            ├── readyState = OPEN (1)
            ├── send(data)  ← 发送消息
            ├── onmessage   ← 接收消息
            └── close(code, reason)
                 └── readyState = CLOSING (2)
                      └── onclose 事件
                           └── readyState = CLOSED (3)

就绪状态常量:

  • WebSocket.CONNECTING = 0:正在连接
  • WebSocket.OPEN = 1:已连接,可通信
  • WebSocket.CLOSING = 2:正在关闭
  • WebSocket.CLOSED = 3:已关闭

7. 其他 HTML5 API 简述

7.1 Notification API(桌面通知)

浏览器支持在桌面右下角弹出系统级通知,即使用户切换到其他标签页也能看到:

7.2 Page Visibility API

用来检测页面是否处于可见状态(用户是否切换到了其他标签页),非常适合用来暂停视频播放或停止动画:

7.3 Network Information API

提供设备的网络连接信息:

7.4 Battery Status API

获取设备的电池状态信息:


总结与最佳实践

API 兼容性速查表

API Chrome Firefox Safari Edge IE
Web Storage ✅ ✅ ✅ ✅ ✅ (IE8+)
Web Worker ✅ ✅ ✅ ✅ ✅ (IE10+)
History API ✅ ✅ ✅ ✅ ✅ (IE10+)
Drag & Drop ✅ ✅ ✅ ✅ ✅ (IE5+)
Geolocation ✅ ✅ ✅ ✅ ✅ (IE9+)
WebSocket ✅ ✅ ✅ ✅ ✅ (IE10+)
Notification ✅ ✅ ✅ (需 HTTPS) ✅ ❌
Page Visibility ✅ ✅ ✅ ✅ ✅ (IE10+)
Network Information ✅ ✅ ❌ ✅ ❌
Battery Status ✅ (已废弃) ✅ ❌ ❌ ❌

注意:Battery Status API 在 Chrome 中已被废弃(deprecated),不建议在新项目中依赖。Network Information API 的兼容性也有限。

项目中使用建议

  1. Web Storage:适合存储用户偏好、缓存数据、购物车等。敏感数据不要存(明文存储),大文件不要存(5MB 限制)。

  2. Web Worker:适合图像处理、数据加密、大文件解析、游戏物理计算等 CPU 密集型任务。DOM 操作不行。

  3. History API:SPA 框架的基石。配合 <base> 标签和服务端路由重定向使用。考虑降级方案支持 hash 路由。

  4. Drag & Drop:移动端支持有限,考虑使用 touch 事件作为补充。

  5. Geolocation:始终提供降级方案(IP 定位或用户手动输入)。尊重用户隐私,只在需要时请求权限。

  6. WebSocket:生产环境务必实现心跳检测、自动重连和消息确认机制。考虑使用 Socket.IO 等库做降级。

  7. 其他 API:使用前检测功能可用性,做好渐进增强。

HTML5 的这些高级 API 将浏览器的能力从文档展示扩展到了全功能应用平台。掌握它们,你的 Web 应用将不再受限于"页面"这个概念,而是真正成为一个跨平台的"应用"。


本文发布于 2024-08-24,分类:前端,标签:HTML5, Web Storage, Web Worker, History API, Drag & Drop, Geolocation, WebSocket