CodeL
以前端为翼,以 AI 为脑,向全栈而行
2026-03-31

前端 Web API 完全指南

前端 Web API 完全指南 零基础也能看懂的浏览器 API 教程,从 DOM 操作到网络请求,从本地存储到实时通信,覆盖前端开发必备的 15+ 个 Web API 目录 1. 核心概念 2. DOM 操作 API 3...

前端 Web API 完全指南 #

零基础也能看懂的浏览器 API 教程,从 DOM 操作到网络请求,从本地存储到实时通信,覆盖前端开发必备的 15+ 个 Web API


目录 #

  1. 核心概念
  2. DOM 操作 API
  3. 网络请求 API
  4. 存储 API
  5. 定时器 API
  6. 路由控制 API
  7. 观察者 API
  8. 剪贴板 API
  9. 通知 API
  10. 其他实用 API
  11. 实战场景
  12. 常见问题
  13. 总结速记

一、核心概念 #

1.1 什么是 Web API?(大白话) #

Web API 是浏览器提供给 JavaScript 的一套"工具包",就像你买了一台电脑,操作系统给你提供了很多工具(文件管理器、截图工具、计算器)。

浏览器也一样,它给你提供了很多"工具",你可以用 JavaScript 调用这些工具:

  • 操作页面元素(DOM API)
  • 发网络请求(Fetch API)
  • 存数据到本地(Storage API)
  • 定时执行任务(Timer API)
  • 获取用户位置(Geolocation API)
  • ...

这些工具统称为 Web API

1.2 Web API vs JavaScript 语法 #

很多人搞不清楚 Web API 和 JavaScript 语法的区别:

类型 属于谁 举例 能在哪用
JavaScript 语法 JS 语言本身 letconstifforfunctionclass 任何 JS 环境(浏览器、Node.js)
Web API 浏览器提供 document.querySelectorfetchlocalStorage 只能在浏览器

简单说:

  • JavaScript 语法 = 语言基础(变量、循环、函数)
  • Web API = 浏览器的"超能力"(操作网页、发请求、存数据)

1.3 Web API 的核心名词 #

名词 大白话解释 举例
DOM Document Object Model,网页的"树形结构" document.body
Element DOM 树上的一个节点(一个 HTML 元素) <div><p>
Node DOM 树上的任何东西(元素、文本、注释) 文本节点、元素节点
Event 事件,用户或浏览器发生的"动作" clickloadscroll
Promise 异步操作的"承诺",未来会完成 fetch() 返回 Promise
async/await Promise 的简化写法 await fetch()
localStorage 本地持久存储,关闭浏览器也不丢失 存用户偏好
sessionStorage 会话存储,关闭标签页就消失 存临时数据

1.4 Web API 的调用方式 #

所有 Web API 都通过 window 对象访问:

// window 是全局对象,浏览器的一切都在它下面
 
// DOM API
window.document.querySelector('.box')
window.alert('提示')
 
// 网络请求
window.fetch('/api/data')
 
// 存储
window.localStorage.setItem('key', 'value')
 
// 定时器
window.setTimeout(() => {}, 1000)
 
// 实际使用时,window 可以省略
document.querySelector('.box')  // 等同于 window.document...
fetch('/api/data')              // 等同于 window.fetch...
localStorage.setItem('key', 'value')  // 等同于 window.localStorage...

1.5 Web API 的异步特性 #

大部分 Web API 是异步的(不会立即返回结果):

同步 API(立即返回) 异步 API(需要等待)
document.querySelector() fetch()
localStorage.getItem() navigator.geolocation.getCurrentPosition()
element.style.color = 'red' navigator.clipboard.readText()

异步 API 的三种处理方式:

// 1. 回调函数(老式写法)
setTimeout(() => {
  console.log('1 秒后执行');
}, 1000);
 
// 2. Promise(现代写法)
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));
 
// 3. async/await(推荐写法)
async function getData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

二、DOM 操作 API #

2.1 什么是 DOM? #

DOM(Document Object Model) 是浏览器把 HTML 解析成的一个"树形结构",JavaScript 可以操作这棵树。

HTML:
<html>
  <body>
    <div class="container">
      <h1>标题</h1>
      <p>段落</p>
    </div>
  </body>
</html>
 
DOM 树:
document
  └── html
       └── body
            └── div.container
                 ├── h1
                 └── p

2.2 获取元素(querySelector) #

最常用的获取元素方法:

// 1. 获取单个元素(返回第一个匹配的)
const box = document.querySelector('.box');          // 通过 class
const title = document.querySelector('#title');      // 通过 id
const h1 = document.querySelector('h1');             // 通过标签名
const firstItem = document.querySelector('ul li');   // 通过组合选择器
 
// 2. 获取多个元素(返回 NodeList 数组)
const items = document.querySelectorAll('.item');    // 所有 .item
const allLi = document.querySelectorAll('li');       // 所有 <li>
 
// 3. 特殊获取方式(快捷方法)
const body = document.body;                          // 直接获取 body
const head = document.head;                          // 直接获取 head
const titleEl = document.title;                      // 页面标题(字符串)
const html = document.documentElement;               // 根元素 <html>

对比老式方法(不推荐):

新方法(推荐) 老方法(不推荐) 说明
querySelector('.box') getElementsByClassName('box') 新方法返回静态列表,老方法返回动态列表
querySelector('#id') getElementById('id') 新方法更灵活

2.3 操作元素内容 #

const box = document.querySelector('.box');
 
// 1. 获取/设置文本内容(纯文本,不含 HTML)
box.textContent;                     // 获取文本
box.textContent = '新文本';           // 设置文本(安全,不会执行脚本)
 
// 2. 获取/设置 HTML 内容(包含 HTML 标签)
box.innerHTML;                       // 获取 HTML
box.innerHTML = '<strong>加粗</strong>';  // 设置 HTML(注意 XSS 风险)
 
// ⚠️ 安全警告:
// 不要用 innerHTML 渲染用户输入的内容,有 XSS 风险
const userInput = '<script>alert("黑客")</script>';
box.innerHTML = userInput;  // ❌ 危险!会执行恶意脚本
box.textContent = userInput;  // ✅ 安全!只显示文本
 
// 3. 获取/设置表单值
const input = document.querySelector('input');
input.value;                         // 获取输入值
input.value = '默认值';               // 设置输入值

2.4 操作元素属性 #

const link = document.querySelector('a');
const img = document.querySelector('img');
 
// 1. 获取/设置标准属性
link.href;                           // 获取 href
link.href = 'https://example.com';   // 设置 href
img.src;                             // 获取 src
img.src = 'new-image.png';           // 设置 src
 
// 2. 获取/设置自定义属性(data-*)
const box = document.querySelector('.box');
box.dataset.userId;                  // 获取 data-user-id
box.dataset.userId = '123';          // 设置 data-user-id
 
// HTML: <div class="box" data-user-id="123" data-role="admin"></div>
// JS: box.dataset.userId === '123', box.dataset.role === 'admin'
 
// 3. 通用属性方法
link.getAttribute('href');           // 获取属性
link.setAttribute('href', 'xxx');    // 设置属性
link.removeAttribute('href');        // 删除属性
link.hasAttribute('href');           // 检查是否有属性

2.5 操作元素样式 #

const box = document.querySelector('.box');
 
// 1. 直接设置 style(适合动态样式)
box.style.color = 'red';             // 设置颜色
box.style.backgroundColor = '#fff';  // 注意:驼峰命名,不是 background-color
box.style.width = '100px';
box.style.display = 'none';          // 隐藏元素
box.style.display = 'block';         // 显示元素
 
// 2. 获取计算后的样式
const styles = window.getComputedStyle(box);
styles.color;                        // 获取实际颜色
styles.width;                        // 获取实际宽度
 
// 3. 操作 class(推荐用于预设样式)
box.className;                       // 获取所有 class(字符串)
box.className = 'box active';        // 设置 class
 
// 4. classList(推荐,更灵活)
box.classList.add('active');         // 添加 class
box.classList.remove('active');      // 删除 class
box.classList.toggle('active');      // 切换(有则删,无则加)
box.classList.contains('active');    // 检查是否有
box.classList.replace('old', 'new'); // 替换

2.6 创建/删除元素 #

// 1. 创建元素
const div = document.createElement('div');  // 创建 <div>
div.className = 'box';
div.textContent = '新盒子';
 
// 2. 添加到页面
const container = document.querySelector('.container');
container.appendChild(div);                 // 添加到最后
container.insertBefore(div, container.firstChild);  // 添加到最前
 
// 3. 新式方法(更灵活)
container.append(div);                      // 添加到最后(可添加多个)
container.prepend(div);                     // 添加到最前
container.before(div);                      // 添加到元素前面
container.after(div);                       // 添加到元素后面
 
// 4. 克隆元素
const clone = div.cloneNode(true);          // true = 深克隆(包含子元素)
const cloneLight = div.cloneNode(false);    // false = 浅克隆(不含子元素)
 
// 5. 删除元素
div.remove();                               // 删除自己
container.removeChild(div);                 // 父元素删除子元素
 
// 6. 替换元素
const newDiv = document.createElement('div');
oldDiv.replaceWith(newDiv);                 // 替换自己
container.replaceChild(newDiv, oldDiv);     // 父元素替换子元素

2.7 事件监听 #

const button = document.querySelector('button');
 
// 1. 添加事件监听(推荐)
button.addEventListener('click', () => {
  console.log('点击了');
});
 
// 可以添加多个监听器
button.addEventListener('click', handler1);
button.addEventListener('click', handler2);
 
// 2. 移除事件监听
const handler = () => console.log('点击');
button.addEventListener('click', handler);
button.removeEventListener('click', handler);  // 必须是同一个函数引用
 
// 3. 一次性监听(触发后自动移除)
button.addEventListener('click', () => {
  console.log('只触发一次');
}, { once: true });
 
// 4. 事件对象
button.addEventListener('click', (event) => {
  event.target;            // 被点击的元素
  event.currentTarget;     // 监听事件的元素
  event.preventDefault();  // 阻止默认行为(如阻止链接跳转)
  event.stopPropagation(); // 阻止事件冒泡
});
 
// 5. 常用事件列表
// 鼠标:click、dblclick、mouseenter、mouseleave、mousemove
// 键盘:keydown、keyup、keypress
// 表单:input、change、submit、focus、blur
// 页面:load、DOMContentLoaded、scroll、resize

三、网络请求 API #

3.1 Fetch API 基础 #

Fetch 是现代浏览器发送网络请求的标准方法:

// 最简单的 GET 请求
fetch('/api/users')
  .then(response => response.json())  // 解析 JSON
  .then(data => console.log(data))
  .catch(err => console.error(err));
 
// async/await 写法(推荐)
async function getUsers() {
  try {
    const response = await fetch('/api/users');
    if (!response.ok) {
      throw new Error(`请求失败: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (err) {
    console.error(err);
  }
}

3.2 不同请求方法 #

// GET 请求(获取数据)
fetch('/api/users');
 
// GET 请求带参数
fetch('/api/users?page=1&limit=10');
// 或用 URLSearchParams
const params = new URLSearchParams({ page: 1, limit: 10 });
fetch(`/api/users?${params}`);
 
// POST 请求(创建数据)
fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: '张三', age: 25 }),
});
 
// PUT 请求(更新数据)
fetch('/api/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: '李四', age: 30 }),
});
 
// DELETE 请求(删除数据)
fetch('/api/users/123', {
  method: 'DELETE',
});

3.3 处理不同响应类型 #

// JSON 响应(最常见)
const response = await fetch('/api/users');
const data = await response.json();
 
// 文本响应
const response = await fetch('/api/message');
const text = await response.text();
 
// Blob 响应(文件、图片)
const response = await fetch('/api/image.png');
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);  // 创建临时 URL
document.querySelector('img').src = imageUrl;
 
// ArrayBuffer 响应(二进制数据)
const response = await fetch('/api/binary');
const buffer = await response.arrayBuffer();

3.4 请求配置详解 #

fetch('/api/users', {
  // 请求方法
  method: 'POST',
  
  // 请求头
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123',
    'Accept': 'application/json',
  },
  
  // 请求体
  body: JSON.stringify({ name: '张三' }),
  
  // 其他配置
  mode: 'cors',              // 跨域模式:cors、no-cors、same-origin
  credentials: 'include',    // 发送 Cookie:include、same-origin、omit
  cache: 'no-cache',         // 缓存策略
  redirect: 'follow',        // 重定向处理
  timeout: 5000,             // ⚠️ Fetch 不支持 timeout,需要用 AbortController
});
 
// 超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
 
try {
  const response = await fetch('/api/users', {
    signal: controller.signal,
  });
  clearTimeout(timeoutId);
  const data = await response.json();
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('请求超时');
  }
}

3.5 错误处理最佳实践 #

async function request(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });
    
    // 检查 HTTP 状态码
    if (!response.ok) {
      const error = await response.json().catch(() => ({ message: '请求失败' }));
      throw new Error(error.message || `HTTP ${response.status}`);
    }
    
    return await response.json();
  } catch (err) {
    // 区分网络错误和其他错误
    if (err.name === 'TypeError' && err.message === 'Failed to fetch') {
      throw new Error('网络错误,请检查网络连接');
    }
    throw err;
  }
}
 
// 使用
try {
  const users = await request('/api/users');
  console.log(users);
} catch (err) {
  console.error(err.message);
}

四、存储 API #

4.1 localStorage vs sessionStorage #

特性 localStorage sessionStorage
生命周期 永久存储,除非手动删除 关闭标签页就删除
作用域 同源的所有标签页共享 仅当前标签页
容量 约 5MB 约 5MB
用途 用户偏好、主题、持久数据 临时数据、表单草稿、单页会话

4.2 localStorage 使用 #

// 1. 存储(只能存字符串)
localStorage.setItem('username', '张三');
localStorage.setItem('theme', 'dark');
 
// 2. 获取
const username = localStorage.getItem('username');  // '张三'
const theme = localStorage.getItem('theme');        // 'dark'
const missing = localStorage.getItem('不存在');     // null
 
// 3. 删除
localStorage.removeItem('username');
 
// 4. 清空所有
localStorage.clear();
 
// 5. 获取数量
localStorage.length;  // 存储的条目数
localStorage.key(0);  // 获取第 0 个 key
 
// ⚠️ 存对象需要先转成 JSON
const user = { name: '张三', age: 25 };
localStorage.setItem('user', JSON.stringify(user));  // 存储
const savedUser = JSON.parse(localStorage.getItem('user'));  // 读取

4.3 sessionStorage 使用 #

// 用法和 localStorage 完全一样,只是生命周期不同
sessionStorage.setItem('temp', '临时数据');
sessionStorage.getItem('temp');
sessionStorage.removeItem('temp');
sessionStorage.clear();
 
// 常见用途:保存表单草稿
const form = { title: '文章标题', content: '文章内容...' };
sessionStorage.setItem('draft', JSON.stringify(form));
 
// 用户关闭标签页后,草稿自动清除

4.4 存储事件监听 #

// 当其他标签页修改 localStorage 时触发
window.addEventListener('storage', (event) => {
  event.key;       // 修改的 key
  event.oldValue;  // 旧值
  event.newValue;  // 新值
  event.url;       // 发生改变的页面 URL
});
 
// 用途:多标签页同步登录状态
// A 标签页登录,设置 localStorage.setItem('token', 'xxx')
// B 标签页监听到 storage 事件,自动刷新页面

4.5 IndexedDB(大数据量存储) #

当 localStorage 不够用时,用 IndexedDB:

// IndexedDB 是浏览器内置的数据库,支持存储大量结构化数据
// 比 localStorage 复杂,但容量更大(可达几百 MB)
 
// 简化用法示例(使用 idb 库)
import { openDB } from 'idb';
 
async function initDB() {
  const db = await openDB('myDatabase', 1, {
    upgrade(db) {
      db.createObjectStore('users', { keyPath: 'id' });
    },
  });
  return db;
}
 
// 存储
async function saveUser(user) {
  const db = await initDB();
  await db.put('users', user);
}
 
// 获取
async function getUser(id) {
  const db = await initDB();
  return await db.get('users', id);
}

五、定时器 API #

5.1 setTimeout(一次性定时器) #

// 基本用法:延迟执行
setTimeout(() => {
  console.log('1 秒后执行');
}, 1000);
 
// 带参数
setTimeout((name) => {
  console.log(`Hello ${name}`);
}, 1000, '张三');
 
// 取消定时器
const timerId = setTimeout(() => {
  console.log('不会执行');
}, 1000);
clearTimeout(timerId);  // 取消
 
// ⚠️ 注意:setTimeout 的延迟不是精确的
// 如果主线程有耗时任务,会延迟执行
console.log('开始');
setTimeout(() => console.log('定时器'), 0);
console.log('结束');
// 输出顺序:开始 → 结束 → 定时器(不是立即执行)

5.2 setInterval(重复定时器) #

// 基本用法:每隔一段时间执行
const timerId = setInterval(() => {
  console.log('每 1 秒执行');
}, 1000);
 
// 取消定时器
clearInterval(timerId);
 
// ⚠️ setInterval 的常见问题
// 1. 如果执行时间 > 间隔时间,会堆积执行
// 2. 推荐用 setTimeout 递归替代
 
// setTimeout 递归实现定时器(推荐)
let count = 0;
function loop() {
  console.log(`第 ${++count} 次`);
  if (count < 5) {
    setTimeout(loop, 1000);
  }
}
loop();

5.3 requestAnimationFrame(动画定时器) #

// 专门用于动画,比 setInterval 更流畅
// 浏览器会在每一帧渲染前调用(约 60fps)
 
function animate() {
  // 更新动画状态
  box.style.left = `${position}px`;
  position += 5;
  
  // 继续下一帧
  if (position < 500) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);
 
// 取消动画
const animId = requestAnimationFrame(animate);
cancelAnimationFrame(animId);
 
// ⚠️ requestAnimationFrame 的优势
// 1. 浏览器优化,动画更流畅
// 2. 页面隐藏时自动暂停,节省资源
// 3. 与屏幕刷新率同步(60fps 或更高)

5.4 定时器最佳实践 #

// ❌ 错误:忘记清理定时器
function Component() {
  setInterval(() => {
    console.log('组件已销毁,但定时器还在运行');
  }, 1000);
}
 
// ✅ 正确:组件销毁时清理
class TimerComponent {
  constructor() {
    this.timerId = setInterval(() => {
      this.update();
    }, 1000);
  }
  
  destroy() {
    clearInterval(this.timerId);  // 清理
  }
}
 
// ✅ React 中的正确做法
function Component() {
  useEffect(() => {
    const timerId = setInterval(() => {
      console.log('运行');
    }, 1000);
    
    return () => clearInterval(timerId);  // 清理函数
  }, []);
}

六、路由控制 API #

6.1 History API(控制浏览器历史) #

// 1. 前进/后退
history.back();      // 后退
history.forward();   // 前进
history.go(-2);      // 后退 2 步
history.go(2);       // 前进 2 步
 
// 2. 添加历史记录(不刷新页面)
history.pushState({ page: 1 }, '标题', '/page1');
// URL 变成 /page1,但不刷新页面
 
// 3. 替换当前历史记录
history.replaceState({ page: 2 }, '标题', '/page2');
// URL 变成 /page2,替换当前记录(不能后退回来)
 
// 4. 监听历史变化(前进/后退时触发)
window.addEventListener('popstate', (event) => {
  console.log(event.state);  // pushState/replaceState 传入的数据
});
 
// ⚠️ 注意:pushState 和 replaceState 不会触发 popstate
// popstate 只在用户点击前进/后退按钮时触发

6.2 Location API(获取/修改 URL) #

// 获取 URL 信息
location.href;       // 完整 URL:https://example.com/path?query=1#hash
location.protocol;   // 协议:https:
location.host;       // 主机:example.com
location.hostname;   // 主机名:example.com
location.port;       // 端口:8080(如果没有端口则为空)
location.pathname;   // 路径:/path
location.search;     // 查询参数:?query=1
location.hash;       // 哈希:#hash
 
// 解析查询参数
const params = new URLSearchParams(location.search);
params.get('query');  // '1'
params.has('query');  // true
params.set('new', 'value');
params.toString();    // 'query=1&new=value'
 
// 修改 URL(会刷新页面)
location.href = 'https://example.com';  // 跳转(有历史记录)
location.assign('https://example.com');  // 同上
location.replace('https://example.com');  // 跳转(无历史记录)
location.reload();  // 刷新当前页面

6.3 Hash 路由示例 #

// 基于 hash 的简单路由(#后面的内容)
// URL:https://example.com/#/home
 
// 监听 hash 变化
window.addEventListener('hashchange', () => {
  const hash = location.hash;  // '#/home'
  const path = hash.slice(1);  // '/home'
  
  // 根据 path 渲染不同内容
  if (path === '/home') {
    renderHome();
  } else if (path === '/about') {
    renderAbout();
  }
});
 
// 跳转
location.hash = '#/about';  // URL 变成 #/about,触发 hashchange

七、观察者 API #

7.1 IntersectionObserver(元素可见性观察) #

用途:懒加载图片、无限滚动、进入动画

// 创建观察者
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    // entry.isIntersecting:元素是否进入视口
    // entry.intersectionRatio:可见比例(0-1)
    // entry.target:被观察的元素
    
    if (entry.isIntersecting) {
      console.log('元素进入视口');
      entry.target.classList.add('visible');
      
      // 如果只需要触发一次,停止观察
      observer.unobserve(entry.target);
    }
  });
}, {
  root: null,              // 相对的容器(null = 视口)
  rootMargin: '0px',       // 容器的边距
  threshold: 0.5,          // 可见比例达到多少触发(0.5 = 50%)
});
 
// 观察元素
const images = document.querySelectorAll('.lazy-image');
images.forEach(img => observer.observe(img));
 
// 懒加载图片示例
const lazyObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;  // 加载真实图片
      lazyObserver.unobserve(img);
    }
  });
});
 
document.querySelectorAll('img[data-src]').forEach(img => {
  lazyObserver.observe(img);
});

7.2 ResizeObserver(元素尺寸观察) #

// 监听元素尺寸变化
const resizeObserver = new ResizeObserver((entries) => {
  entries.forEach(entry => {
    const { width, height } = entry.contentRect;
    console.log(`尺寸变化: ${width} x ${height}`);
    
    // 响应式处理
    if (width < 600) {
      entry.target.classList.add('mobile');
    } else {
      entry.target.classList.remove('mobile');
    }
  });
});
 
// 观察元素
resizeObserver.observe(document.querySelector('.container'));
 
// ⚠️ 注意:ResizeObserver 可以观察任何元素,不只是 window

7.3 MutationObserver(DOM 变化观察) #

// 监听 DOM 结构变化(子元素增删、属性变化)
const mutationObserver = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    // mutation.type:变化类型(childList、attributes、characterData)
    // mutation.target:发生变化的元素
    // mutation.addedNodes:新增的节点
    // mutation.removedNodes:删除的节点
    
    console.log('DOM 变化:', mutation.type);
  });
});
 
// 配置观察选项
mutationObserver.observe(document.querySelector('.container'), {
  childList: true,     // 观察子元素变化
  attributes: true,    // 观察属性变化
  subtree: true,       // 观察所有后代元素
  attributeOldValue: true,  // 记录旧属性值
});
 
// 停止观察
mutationObserver.disconnect();

八、剪贴板 API #

8.1 Clipboard API(读写剪贴板) #

// 1. 写入剪贴板
async function copyText(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log('已复制');
  } catch (err) {
    console.error('复制失败:', err);
  }
}
 
// 使用
copyText('要复制的内容');
 
// 2. 读取剪贴板
async function readClipboard() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('剪贴板内容:', text);
  } catch (err) {
    console.error('读取失败:', err);  // 用户可能拒绝授权
  }
}
 
// 3. 写入多种内容(图片等)
async function copyImage(blob) {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        'image/png': blob,
      }),
    ]);
    console.log('图片已复制');
  } catch (err) {
    console.error('复制失败:', err);
  }
}
 
// ⚠️ 注意:
// 1. 需要 HTTPS 或 localhost
// 2. 读取剪贴板需要用户授权(浏览器会弹出提示)

8.2 老式复制方法(兼容方案) #

// 兼容不支持 Clipboard API 的浏览器
function copyTextCompat(text) {
  // 创建临时输入框
  const input = document.createElement('input');
  input.value = text;
  document.body.appendChild(input);
  
  // 选中并复制
  input.select();
  document.execCommand('copy');  // 老式方法
  
  // 清理
  document.body.removeChild(input);
  console.log('已复制');
}
 
// 判断是否支持新 API
if (navigator.clipboard) {
  navigator.clipboard.writeText(text);
} else {
  copyTextCompat(text);
}

九、通知 API #

9.1 Notification API(浏览器通知) #

// 1. 请求通知权限
async function requestPermission() {
  const permission = await Notification.requestPermission();
  // permission: 'granted'、'denied'、'default'
  
  if (permission === 'granted') {
    console.log('已授权');
  } else {
    console.log('用户拒绝');
  }
}
 
// 2. 发送通知
function showNotification(title, options = {}) {
  if (Notification.permission === 'granted') {
    const notification = new Notification(title, {
      body: '通知内容',
      icon: '/icon.png',
      badge: '/badge.png',
      tag: 'unique-id',    // 相同 tag 会替换旧通知
      requireInteraction: false,  // 是否需要用户点击关闭
      ...options,
    });
    
    // 点击通知
    notification.onclick = () => {
      window.focus();
      notification.close();
    };
    
    // 通知关闭
    notification.onclose = () => {
      console.log('通知关闭');
    };
  }
}
 
// 使用
requestPermission();
showNotification('新消息', { body: '你有一条新消息' });
 
// ⚠️ 注意:
// 1. 需要 HTTPS 或 localhost
// 2. 需要用户授权
// 3. 页面隐藏时通知更有效(用户能看到)

十、其他实用 API #

10.1 Geolocation(获取位置) #

// 获取用户位置
navigator.geolocation.getCurrentPosition(
  (position) => {
    const { latitude, longitude } = position.coords;
    console.log(`位置: ${latitude}, ${longitude}`);
  },
  (error) => {
    console.error('获取位置失败:', error.message);
  },
  {
    enableHighAccuracy: true,  // 高精度
    timeout: 10000,            // 超时时间
    maximumAge: 0,             // 不使用缓存位置
  }
);
 
// 持续监听位置变化
const watchId = navigator.geolocation.watchPosition(
  (position) => {
    console.log(`移动到: ${position.coords.latitude}, ${position.coords.longitude}`);
  },
  (error) => console.error(error),
  { enableHighAccuracy: true }
);
 
// 停止监听
navigator.geolocation.clearWatch(watchId);
 
// ⚠️ 注意:需要用户授权

10.2 WebSocket(实时通信) #

// 创建 WebSocket 连接
const ws = new WebSocket('wss://example.com/socket');
 
// 连接成功
ws.onopen = () => {
  console.log('连接成功');
  ws.send('Hello');  // 发送消息
};
 
// 收到消息
ws.onmessage = (event) => {
  console.log('收到消息:', event.data);
  
  // 如果是二进制数据
  if (event.data instanceof Blob) {
    // 处理二进制
  }
};
 
// 连接关闭
ws.onclose = (event) => {
  console.log('连接关闭:', event.code, event.reason);
};
 
// 连接错误
ws.onerror = (error) => {
  console.error('连接错误:', error);
};
 
// 发送数据
ws.send('文本消息');
ws.send(new Blob(['二进制数据']));
ws.send(new ArrayBuffer(10));
 
// 关闭连接
ws.close(1000, '正常关闭');  // 1000 = 正常关闭码

10.3 Web Workers(后台线程) #

// 主线程
const worker = new Worker('worker.js');
 
// 发送消息给 Worker
worker.postMessage({ type: 'calculate', data: [1, 2, 3, 4, 5] });
 
// 收到 Worker 的消息
worker.onmessage = (event) => {
  console.log('Worker 结果:', event.data);
};
 
// Worker 错误
worker.onerror = (error) => {
  console.error('Worker 错误:', error);
};
 
// 终止 Worker
worker.terminate();
 
// worker.js(Worker 线程)
self.onmessage = (event) => {
  const { type, data } = event.data;
  
  if (type === 'calculate') {
    // 执行耗时计算(不阻塞主线程)
    const result = data.reduce((sum, n) => sum + n, 0);
    
    // 返回结果
    self.postMessage({ type: 'result', data: result });
  }
};
 
// ⚠️ 注意:
// 1. Worker 不能访问 DOM(document、window)
// 2. Worker 不能访问 localStorage
// 3. Worker 可以用 fetch、WebSocket

10.4 Performance API(性能监控) #

// 1. 测量代码执行时间
console.time('计算');
for (let i = 0; i < 1000000; i++) {}
console.timeEnd('计算');  // 输出耗时
 
// 2. Performance API(更精确)
const start = performance.now();
// ... 执行代码
const end = performance.now();
console.log(`耗时: ${end - start}ms`);
 
// 3. 获取页面性能数据
const timing = performance.timing;
timing.navigationStart;  // 页面开始加载时间
timing.domContentLoadedEventEnd;  // DOM 加载完成时间
timing.loadEventEnd;  // 页面完全加载时间
 
// 页面加载耗时
const loadTime = timing.loadEventEnd - timing.navigationStart;
console.log(`页面加载耗时: ${loadTime}ms`);
 
// 4. 用户自定义性能标记
performance.mark('start');
// ... 执行代码
performance.mark('end');
performance.measure('耗时', 'start', 'end');
 
const measures = performance.getEntriesByName('耗时');
console.log(measures[0].duration);  // 耗时(毫秒)

10.5 Fullscreen API(全屏) #

// 进入全屏
async function enterFullscreen(element) {
  try {
    await element.requestFullscreen();
    console.log('进入全屏');
  } catch (err) {
    console.error('全屏失败:', err);
  }
}
 
// 使用
enterFullscreen(document.querySelector('video'));
enterFullscreen(document.documentElement);  // 整页全屏
 
// 退出全屏
async function exitFullscreen() {
  try {
    await document.exitFullscreen();
    console.log('退出全屏');
  } catch (err) {
    console.error('退出失败:', err);
  }
}
 
// 监听全屏变化
document.addEventListener('fullscreenchange', () => {
  if (document.fullscreenElement) {
    console.log('当前全屏元素:', document.fullscreenElement);
  } else {
    console.log('已退出全屏');
  }
});

10.6 Page Visibility API(页面可见性) #

// 监听页面可见性变化(用户切换标签页)
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    console.log('页面隐藏(用户切换标签页)');
    // 暂停视频、停止动画等
  } else {
    console.log('页面可见');
    // 恢复视频、继续动画等
  }
});
 
// 检查当前状态
document.hidden;  // true = 隐藏,false = 可见
document.visibilityState;  // 'visible'、'hidden'、'prerender'

十一、实战场景 #

场景 1:表单自动保存草稿 #

// 需求:用户填写表单时自动保存,防止意外关闭丢失
 
class FormDraft {
  constructor(formId) {
    this.form = document.querySelector(formId);
    this.draftKey = `draft_${formId}`;
    
    // 加载草稿
    this.loadDraft();
    
    // 监听输入,自动保存
    this.form.addEventListener('input', () => {
      this.saveDraft();
    });
    
    // 提交成功后清除草稿
    this.form.addEventListener('submit', () => {
      this.clearDraft();
    });
  }
  
  // 保存草稿
  saveDraft() {
    const formData = new FormData(this.form);
    const data = {};
    
    for (const [key, value] of formData.entries()) {
      data[key] = value;
    }
    
    sessionStorage.setItem(this.draftKey, JSON.stringify(data));
    console.log('草稿已保存');
  }
  
  // 加载草稿
  loadDraft() {
    const saved = sessionStorage.getItem(this.draftKey);
    if (!saved) return;
    
    const data = JSON.parse(saved);
    
    // 恢复表单值
    for (const [key, value] of Object.entries(data)) {
      const field = this.form.querySelector(`[name="${key}"]`);
      if (field) {
        field.value = value;
      }
    }
    
    console.log('草稿已恢复');
  }
  
  // 清除草稿
  clearDraft() {
    sessionStorage.removeItem(this.draftKey);
    console.log('草稿已清除');
  }
}
 
// 使用
new FormDraft('#article-form');

场景 2:图片懒加载 #

// 需求:页面有大量图片,只加载用户能看到的部分
 
// HTML
// <img class="lazy" data-src="real-image.jpg" src="placeholder.jpg">
 
function setupLazyLoad() {
  const images = document.querySelectorAll('img.lazy');
  
  // 创建观察者
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        
        // 加载真实图片
        img.src = img.dataset.src;
        img.classList.remove('lazy');
        
        // 停止观察
        observer.unobserve(img);
      }
    });
  }, {
    rootMargin: '50px',  // 提前 50px 开始加载
  });
  
  // 观察所有图片
  images.forEach(img => observer.observe(img));
}
 
// 初始化
setupLazyLoad();

场景 3:实时数据同步 #

// 求:多标签页同步登录状态
 
// A 标签页登录
function login(token) {
  localStorage.setItem('token', token);
  localStorage.setItem('loginTime', Date.now());
  
  // 触发 storage 事件,通知其他标签页
  // 注意:当前标签页不会收到自己的 storage 事件
}
 
// 所有标签页监听
window.addEventListener('storage', (event) => {
  if (event.key === 'token') {
    if (event.newValue) {
      // 检测到登录
      console.log('其他标签页已登录');
      refreshPage();
    } else {
      // 检测到退出
      console.log('其他标签页已退出');
      redirectToLogin();
    }
  }
});
 
// 退出登录
function logout() {
  localStorage.removeItem('token');
  localStorage.removeItem('loginTime');
  // 其他标签页会收到 storage 事件,自动退出
}

场景 4:无限滚动加载 #

// 需求:滚动到底部自动加载更多数据
 
class InfiniteScroll {
  constructor(options) {
    this.container = document.querySelector(options.container);
    this.loader = document.querySelector(options.loader);
    this.onLoadMore = options.onLoadMore;
    this.isLoading = false;
    this.hasMore = true;
    
    this.setupObserver();
  }
  
  setupObserver() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        // loader 进入视口 = 用户滚动到底部
        if (entry.isIntersecting && !this.isLoading && this.hasMore) {
          this.loadMore();
        }
      });
    });
    
    observer.observe(this.loader);
  }
  
  async loadMore() {
    this.isLoading = true;
    this.loader.textContent = '加载中...';
    
    try {
      const data = await this.onLoadMore();
      
      if (data.length === 0) {
        this.hasMore = false;
        this.loader.textContent = '没有更多了';
      } else {
        this.renderItems(data);
      }
    } catch (err) {
      console.error('加载失败:', err);
      this.loader.textContent = '加载失败,点击重试';
      this.loader.onclick = () => this.loadMore();
    }
    
    this.isLoading = false;
  }
  
  renderItems(items) {
    items.forEach(item => {
      const element = document.createElement('div');
      element.className = 'item';
      element.textContent = item.title;
      this.container.appendChild(element);
    });
  }
}
 
// 使用
new InfiniteScroll({
  container: '.list',
  loader: '.loader',
  onLoadMore: async () => {
    const response = await fetch(`/api/items?page=${page++}`);
    return await response.json();
  },
});

场景 5:网络请求封装 #

// 需求:统一的网络请求封装,支持超时、重试、错误处理
 
class HttpClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.defaultTimeout = 10000;
  }
  
  async request(url, options = {}) {
    const controller = new AbortController();
    const timeout = options.timeout || this.defaultTimeout;
    
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
      const response = await fetch(`${this.baseURL}${url}`, {
        ...options,
        signal: controller.signal,
        headers: {
          'Content-Type': 'application/json',
          ...options.headers,
        },
      });
      
      clearTimeout(timeoutId);
      
      if (!response.ok) {
        const error = await response.json().catch(() => ({ message: '请求失败' }));
        throw new Error(error.message || `HTTP ${response.status}`);
      }
      
      return await response.json();
    } catch (err) {
      clearTimeout(timeoutId);
      
      if (err.name === 'AbortError') {
        throw new Error('请求超时');
      }
      
      if (err.name === 'TypeError' && err.message.includes('fetch')) {
        throw new Error('网络错误');
      }
      
      throw err;
    }
  }
  
  // 带重试的请求
  async requestWithRetry(url, options = {}, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await this.request(url, options);
      } catch (err) {
        if (i === maxRetries - 1) throw err;
        
        // 等待后重试
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        console.log(`第 ${i + 1} 次重试...`);
      }
    }
  }
  
  // GET
  get(url, options = {}) {
    return this.request(url, { ...options, method: 'GET' });
  }
  
  // POST
  post(url, data, options = {}) {
    return this.request(url, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data),
    });
  }
}
 
// 使用
const api = new HttpClient('/api');
 
async function getUsers() {
  try {
    const users = await api.get('/users');
    console.log(users);
  } catch (err) {
    console.error(err.message);
  }
}
 
async function createUser(data) {
  try {
    const user = await api.post('/users', data);
    console.log('创建成功:', user);
  } catch (err) {
    console.error(err.message);
  }
}
 
// 带重试
async function fetchWithRetry() {
  const data = await api.requestWithRetry('/users', {}, 3);
  console.log(data);
}

十二、常见问题 #

特性 localStorage cookie
容量 5MB 4KB
发送到服务器 不发送 每次请求都发送
过期时间 永久 可设置过期时间
用途 本地数据存储 会话标识、用户追踪

建议:

  • 存数据用 localStorage(容量大)
  • 存会话 token 用 cookie(自动发送)或 localStorage + header

Q2:Fetch 请求为什么有时会 CORS 报错? #

CORS(跨域资源共享)是浏览器安全限制:

场景 是否 CORS 报错
同源请求(相同域名) 不报错
跨域请求(服务器允许) 不报错
跨域请求(服务器不允许) 报错

解决方案:

// 1. 服务器设置 CORS 头
// Access-Control-Allow-Origin: *
 
// 2. 使用代理(开发环境)
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': 'https://target-server.com',
    },
  },
};
 
// 3. 使用 CORS mode(需要服务器配合)
fetch('https://other-server.com/api', {
  mode: 'cors',
});

Q3:setTimeout 0 为什么不是立即执行? #

console.log('开始');
setTimeout(() => console.log('定时器'), 0);
console.log('结束');
 
// 输出:开始 → 结束 → 定时器

原因: JavaScript 是单线程,setTimeout 把任务放到"任务队列",等主线程执行完再执行。

Q4:如何判断元素是否在视口内? #

// 方法 1:IntersectionObserver(推荐)
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('元素在视口内');
    }
  });
});
observer.observe(element);
 
// 方法 2:getBoundingClientRect(老方法)
function isInViewport(element) {
  const rect = element.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= window.innerHeight &&
    rect.right <= window.innerWidth
  );
}

Q5:Web Worker 能操作 DOM 吗? #

不能。 Web Worker 在独立线程,不能访问:

  • document
  • window
  • localStorage
  • DOM 元素

Worker 能做的:

  • 计算密集任务
  • fetch 请求
  • WebSocket
  • IndexedDB

Q6:如何处理 localStorage 容量超出? #

// 检测容量
function getStorageSize() {
  let total = 0;
  for (const key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      total += localStorage.getItem(key).length;
    }
  }
  return total;  // 字符数(约 = 字节数,UTF-16 每字符 2 字节)
}
 
// 写入时检测
function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, value);
  } catch (err) {
    if (err.name === 'QuotaExceededError') {
      console.log('存储空间已满');
      // 清理旧数据
      localStorage.removeItem('oldData');
      // 重试
      localStorage.setItem(key, value);
    }
  }
}

Q7:Fetch 如何取消请求? #

// AbortController
const controller = new AbortController();
 
fetch('/api/users', {
  signal: controller.signal,
});
 
// 取消请求
controller.abort();  // 触发 AbortError
 
// 带超时
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

十三、总结速记 #

Web API 分类速记表 #

类别 API 用途
DOM querySelector、createElement、addEventListener 操作页面元素
网络 fetch、WebSocket 发请求、实时通信
存储 localStorage、sessionStorage、IndexedDB 本地存储数据
定时器 setTimeout、setInterval、requestAnimationFrame 延迟执行、动画
路由 history.pushState、location.hash 控制页面跳转
观察者 IntersectionObserver、ResizeObserver 监听元素状态
剪贴板 navigator.clipboard 复制粘贴
通知 Notification 浏览器通知
位置 navigator.geolocation 获取用户位置
后台 Web Worker 后台线程计算
性能 performance.now() 测量执行时间
全屏 requestFullscreen 进入全屏
可见性 visibilitychange 监听页面切换

DOM API 常用速记 #

// 获取
querySelector('.box')          // 单个
querySelectorAll('.item')      // 多个
 
// 内容
textContent                    // 文本(安全)
innerHTML                      // HTML(注意 XSS)
 
// 样式
style.color = 'red'            // 直接样式
classList.add('active')        // class 操作
 
// 创建/删除
createElement('div')           // 创建
appendChild(div)               // 添加
remove()                       // 删除
 
// 事件
addEventListener('click', fn)  // 添加监听
removeEventListener('click', fn)  // 移除

Fetch API 速记 #

// GET
fetch('/api/users')
 
// POST
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: '张三' }),
})
 
// 处理响应
const data = await response.json()
 
// 错误处理
if (!response.ok) throw new Error('请求失败')

存储 API 速记 #

// localStorage(永久)
localStorage.setItem('key', 'value')
localStorage.getItem('key')
localStorage.removeItem('key')
 
// sessionStorage(会话)
sessionStorage.setItem('key', 'value')
 
// 存对象
localStorage.setItem('obj', JSON.stringify({ a: 1 }))
JSON.parse(localStorage.getItem('obj'))

定时器速记 #

setTimeout(() => {}, 1000)     // 延迟 1 秒
setInterval(() => {}, 1000)    // 每 1 秒
requestAnimationFrame(fn)      // 动画帧
 
clearTimeout(id)               // 取消
clearInterval(id)              // 取消
cancelAnimationFrame(id)       // 取消

附录:API 兼容性速查 #

API Chrome Firefox Safari Edge 移动端
fetch ✅ 全支持
localStorage ✅ 全支持
IntersectionObserver ✅ 51+ ✅ 55+ ✅ 12.1+ ✅ 15+
Clipboard API ✅ 66+ ✅ 63+ ✅ 13.1+ ✅ 79+ ⚠️ 部分
Notification ✅ 全支持 ⚠️ 需授权 ⚠️ 有限
Web Worker ✅ 全支持
WebSocket ✅ 全支持
requestFullscreen ✅ 全支持 ⚠️ 需用户触发

兼容性检查网站:


最后更新:2026-03-29