← 返回首页
文章
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.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 语言本身 | let、const、if、for、function、class |
任何 JS 环境(浏览器、Node.js) |
| Web API | 浏览器提供 | document.querySelector、fetch、localStorage |
只能在浏览器 |
简单说:
- JavaScript 语法 = 语言基础(变量、循环、函数)
- Web API = 浏览器的"超能力"(操作网页、发请求、存数据)
1.3 Web API 的核心名词 #
| 名词 | 大白话解释 | 举例 |
|---|---|---|
| DOM | Document Object Model,网页的"树形结构" | document.body |
| Element | DOM 树上的一个节点(一个 HTML 元素) | <div>、<p> |
| Node | DOM 树上的任何东西(元素、文本、注释) | 文本节点、元素节点 |
| Event | 事件,用户或浏览器发生的"动作" | click、load、scroll |
| 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
└── p2.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 可以观察任何元素,不只是 window7.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、WebSocket10.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);
}十二、常见问题 #
Q1:localStorage 和 cookie 有什么区别? #
| 特性 | 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
继续阅读
返回文章列表