前端 20 个零依赖浏览器原生 API 实战清单
1. ResizeObserver
ResizeObserver
用于监听元素的尺寸变化。
简单来说:它能在元素的宽高变化时(包括内容撑开、CSS 改变、父容器变化等)自动触发回调。
1.1. 语法
const observer = new ResizeObserver(entries => {for (let entry of entries) {console.log('元素尺寸变化了:')console.log('contentRect:', entry.contentRect)console.log('宽度:', entry.contentRect.width)console.log('高度:', entry.contentRect.height)}
})observer.observe(document.querySelector('.box'))
参数说明:
entries
:一个数组,每个元素是一个观察对象(ResizeObserverEntry
)。entry.contentRect
:包含目标元素的内容区域大小。observer.observe(el)
:开始观察某个元素。observer.unobserve(el)
:停止观察某个元素。observer.disconnect()
:断开所有观察。
1.2. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>ResizeObserver Demo</title><style>.box {width: 200px;height: 100px;background-color: lightcoral;resize: both; /* 允许用户拖拽改变大小 */overflow: auto;padding: 10px;}</style></head><body><div class="box">拖拽我改变大小</div><script>const box = document.querySelector('.box')const observer = new ResizeObserver(entries => {for (let entry of entries) {console.log('新尺寸:', entry.contentRect.width, entry.contentRect.height)}})observer.observe(box)</script></body>
</html>
1.3. 使用场景
- 自适应布局:元素大小变化时动态调整字体、图片尺寸等
- 图表组件:容器大小变化时重新渲染 Echarts、D3 图
- 动态内容:监控富文本内容区域变化触发滚动或对齐逻辑
- 虚拟滚动:当容器高度变化时重新计算可视区域范围
- 动画效果:元素尺寸发生变化触发动画或者过度效果
2. IntersectionObserver
IntersectionObserver
用来 监听元素是否进入(或离开)可视区域。
它可以在不监听滚动事件、不计算位置的情况下,高效检测元素是否出现在视口中
2.1. 语法
const observer = new IntersectionObserver((entries, observer) => {entries.forEach(entry => {if (entry.isIntersecting) {console.log('元素进入视口!', entry.target)} else {console.log('元素离开视口!', entry.target)}})
}, {root: null, // 观察的滚动容器,null 表示视口threshold: 0.1 // 触发回调的阈值(可见比例)
})// 开始观察目标元素
observer.observe(document.querySelector('.box'))
参数说明:
entries
:被观察元素的信息列表entry.isIntersecting
:元素是否进入可视区entry.intersectionRatio
:元素可见比例(0~1)entry.target
:当前被观察的 DOM 元素root
:可滚动容器(默认为浏览器视口)threshold
:元素可见比例达到多少触发(如0.5
表示一半可见)
2.2. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><title>IntersectionObserver Demo</title><style>body {margin: 0;font-family: sans-serif;}.spacer {height: 100vh; /* 模拟滚动空间 */background: #eee;}.box {width: 200px;height: 200px;margin: 100px auto;background-color: tomato;transition: transform 0.5s, opacity 0.5s;opacity: 0;transform: scale(0.8);}.visible {opacity: 1;transform: scale(1);}</style></head><body><div class="spacer">向下滚动</div><div class="box">观察我</div><div class="spacer"></div><script>const box = document.querySelector(".box");const observer = new IntersectionObserver(entries => {entries.forEach(entry => {if (entry.isIntersecting) {box.classList.add("visible");console.log("进入可视区");} else {box.classList.remove("visible");console.log("离开可视区");}});},{threshold: 0.2, // 元素有20%可见时触发});observer.observe(box);</script></body>
</html>
2.3. 使用场景
- 图片懒加载:只有当图片进入视口时才加载真正的图片
- 无限滚动加载:页面滚动到底部时加载新数据
- 曝光埋点:广告或模块进入视口时统计曝光次数
- 动画触发:元素进入视口时触发淡入或淡出效果
- 页面定位:滚动时判断当前在哪个内容段落
3. Page Visibility
Page Visibility 用于判断网页当前是否在用户可见的状态。
比如:
- 用户切换到了其他页面
- 用户最小化浏览器
- 用户返回后重新看到当前页面
3.1. 语法
document.addEventListener('visibilitychange', () => {if (document.visibilityState === 'hidden') {console.log('页面隐藏了!')} else {console.log('页面可见了!')}
})
取值如下:
- visible:页面当前可见(在前台)
- hidden:页面不可见(被最小化或切换到其他标签页)
- prerender:页面正在预渲染。
3.2. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>Page Visibility Demo</title></head><body><h1>切换标签页看看控制台输出</h1><script>document.addEventListener('visibilitychange', () => {if (document.visibilityState === 'hidden') {console.log('页面隐藏了!')} else {console.log('页面可见了!')}})</script></body>
</html>
3.3. 使用场景
- 视频播放优化:页面隐藏时暂停视频,返回后恢复播放
- 定时任务暂停:页面隐藏时暂停 setInterval 计时器
- 数据上报节省资源:页面隐藏时停止心跳或轮询请求
- 游戏性能优化:页面暂停时暂停动画、音效等逻辑
- 节能优化:减少 CPU 和内存消耗
4. Web Share
Web Share API 允许网页使用原生的分享界面(通常是手机系统的原生分享面板),用户可以把网页内容分享到其他应用。
简单来说:它让网页像 App 一样,可以“调用系统分享功能”。
4.1. 语法
if (navigator.share) {navigator.share({title: '我的网站标题',text: '来看看这个网站!',url: 'https://example.com'}).then(() => console.log('分享成功')).catch((err) => console.error('分享失败:', err));
} else {console.warn('当前浏览器不支持 Web Share API');
}
navigator.share()
的参数是一个对象,可以包含以下字段:
字段 | 类型 | 说明 |
| string | 分享标题 |
| string | 分享文本 |
| string | 要分享的链接 |
| File[] | 可选,支持分享图片/视频文件(需HTTPS) |
4.2. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>原生 Web Share API 示例</title></head><body><button id="shareBtn">分享当前页面</button><script>const shareBtn = document.getElementById("shareBtn");shareBtn.addEventListener("click", async () => {if (navigator.share) {try {await navigator.share({title: document.title,text: "快来看看这个有趣的页面!",url: window.location.href,});console.log("用户已完成分享");} catch (err) {console.error("用户取消或分享失败:", err);}} else {alert("当前浏览器不支持 Web Share API");}});</script></body>
</html>
5. Wake Lock
Wake Lock API(唤醒锁)可以防止设备因为“无操作”而自动关闭屏幕或进入休眠。目前主要支持屏幕唤醒锁(screen)。
5.1. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Wake Lock Demo</title></head><body><h2>Wake Lock API 示例</h2><button id="lockBtn">保持唤醒</button><button id="unlockBtn">释放唤醒</button><script>let wakeLock = null;// 请求唤醒锁async function requestWakeLock() {try {wakeLock = await navigator.wakeLock.request("screen");console.log("屏幕唤醒锁已启用");wakeLock.addEventListener("release", () => {console.log("唤醒锁已被释放");});} catch (err) {console.error("请求唤醒锁失败:", err);}}// 释放唤醒锁function releaseWakeLock() {if (wakeLock) {wakeLock.release();wakeLock = null;}}// 页面可见性变化时自动恢复锁(浏览器切出会自动失效)document.addEventListener("visibilitychange", () => {if (wakeLock !== null && document.visibilityState === "visible") {requestWakeLock();}});// 绑定按钮document.getElementById("lockBtn").addEventListener("click", requestWakeLock);document.getElementById("unlockBtn").addEventListener("click", releaseWakeLock);</script></body>
</html>
讲解:
- navigator.wakeLock.request('screen'):申请一个保持屏幕唤醒的锁
- wakeLock.release():主动释放锁
- wakeLock.addEventListener('release', …):监听系统自动释放锁(比如切换标签页)
- document.visibilitychange:当页面重新可见时重新请求锁(防止切出去回来实效)
5.2. 注意事项
- 只在 HTTPS 环境可用(或 localhost)
- 多数现代浏览器(如 Chrome、Edge、Android WebView)支持,IOS Safari 目前支持较差
- 必须在用户交互(点击按钮触发),否则会报错
5.3. 应用场景
- 视频播放页面(防止看视频的时候屏幕黑掉)
- 阅读器/在线文档
- 导航类网页
- 运动、计时类 Web App
6. BroadcastChannel
BroadcastChannel 允许在多个浏览器上下文(tab、iframe、web worker)直接进行消息广播。所有使用相同频道名(channel name)的页面都能互相通信
简单来说:它就像浏览器内的“广播电台”——所有收听同一个频道名的页面都能收到同样的消息。
6.1. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><title>BroadcastChannel Demo</title></head><body><h2>🛰 BroadcastChannel 示例</h2><input type="text" id="msgInput" placeholder="输入消息后回车广播" /><div id="log"></div><script>// 1️. 创建频道(同名频道可以互相通信)const channel = new BroadcastChannel("chat-channel");const log = document.getElementById("log");// 2️. 接收来自其他页面的消息channel.onmessage = event => {const msg = event.data;log.innerHTML += `<p>收到消息:${msg}</p>`;};// 3️. 输入框发送消息document.getElementById("msgInput").addEventListener("change", e => {const message = e.target.value;channel.postMessage(message); // 广播给所有订阅此频道的页面e.target.value = "";});</script></body>
</html>
API 详解
- new BroadcastChannel(name):创建一个频道对象,参数时频道名(字符串)
- channel.postMessage(data):向频道广播一条消息
- channel.onmessage = handler:监听频道收到的消息
- channel.close():关闭当前频道
6.2. 特点与限制
特点 | 说明 |
实时通信 | 同源标签页、iframe、Web Worker 均可共享 |
同源限制 | 只能在相同协议 + 域名 + 端口的上下文间通信 |
不能跨浏览器 | 只在当前浏览器进程有效(不同浏览器或隐私模式之间不共享) |
生命周期短 | 页面关闭或 后会自动失效 |
6.3. 应用场景
- 多标签页同步登陆状态:登陆或退出后,其他标签自动同步状态
- 多窗口聊天::多页面共享聊天内容
- 多标签页操作同步:设置项、主题切换实时同步
- 与 Web Worker 通信:主线程和 Worker 间广播状态变化
7. PerformanceObserver
PerformanceObserver
用于监听性能事件(performance entries),这些是浏览器在运行过程中自动记录的,比如:
- 页面加载阶段的指标(如 navigation)
- 资源加载(如 resource)
- 长任务(如 longtask)
- 渲染阶段(如 paint)
简单理解:它是浏览器提供的性能监听器,可以实时获取数据而不需要手动打断点
7.1. 语法
// 创建一个性能观察者
const observer = new PerformanceObserver((list) => {const entries = list.getEntries()for (const entry of entries) {console.log('性能事件:', entry)}
})// 监听的类型,比如资源加载、页面导航等
observer.observe({ type: 'resource', buffered: true })
7.2. 常见可观察类型
类型名 | 说明 | 示例字段 |
| 外部资源加载性能(CSS、JS、图片等) |
|
| 页面加载性能(HTML 解析、DNS、TCP、DOM 等) |
|
| 渲染阶段性能(首次绘制 FP、首次内容绘制 FCP) |
|
| 长任务检测(主线程卡顿 >50ms) |
|
| 最大内容绘制(LCP) | 用于 Core Web Vitals |
| 布局偏移(CLS) |
|
7.3. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><title>PerformanceObserver Demo</title></head><body><h1>性能观察 Demo</h1><img src="https://picsum.photos/800/400" /><script>// 监听页面加载性能const perfObserver = new PerformanceObserver((list) => {list.getEntries().forEach(entry => {console.log(`[${entry.entryType}]`, entry.name, entry.duration)})})perfObserver.observe({ type: 'resource', buffered: true }) // 资源加载perfObserver.observe({ type: 'paint', buffered: true }) // 首次绘制perfObserver.observe({ type: 'navigation', buffered: true }) // 页面加载</script></body>
</html>
7.4. 应用场景
- 页面加载性能监控:统计首屏加载时间、FCP、LCP
- 资源加载监控:判断哪些资源加载慢
- 性能上报:配合上报系统发送到后端分析
- 检测卡顿:使用‘longtask’监控主线程阻塞
- 用户体验分析:跟踪 CLS(内容抖动)、FID(首次交互延迟)
与 performance.getEntriesByType()
的区别?
performance.getEntriesByType('resource'):一次性获取当前已有的性能数据
performanceObserver:可以实时监听后续加载的性能数据(如懒加载图片)
前者是静态采样,后者是动态监听
8. requestIdleCallback
requestIdleCallback 会在浏览器“空闲”的时候调用你提供的回调函数。也就是说,当浏览器完成了渲染、用户交互、动画这些更重要的任务后,会在“有空”的时候来处理提交的低优先级任务。
8.1. 基本语法
const handle = requestIdleCallback(callback[, options])
cancelIdleCallback(handle)
参数解释:
- callback(deadline):空闲时执行的函数,接收一个 deadline 参数
- options.timeout:超时时间(如果太久没有空闲出来也强制执行)
requestIdleCallback(myTask, { timeout: 2000 })
deadline 对象解析
- timeRemaining:返回当前帧剩余的空闲时间(毫秒)
- dedTimeout:是否因超时被强制执行
8.2. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>requestIdleCallback Demo</title></head><body><h2>requestIdleCallback 示例</h2><p>打开控制台,看看浏览器“空闲”时才执行的任务。</p><script>const tasks = Array.from({ length: 10 }, (_, i) => `任务 ${i + 1}`)function processTasks(deadline) {while (deadline.timeRemaining() > 0 && tasks.length > 0) {const task = tasks.shift()console.log(`执行:${task}`)}if (tasks.length > 0) {// 还有任务没完成,继续安排下一个空闲回调requestIdleCallback(processTasks)} else {console.log('所有任务完成')}}// 注册第一个空闲回调requestIdleCallback(processTasks)</script></body>
</html>
运行效果:
浏览器在滚动、渲染、动画时不会执行任务,
当停止交互、主线程空闲时,控制台才逐个打印任务执行。
8.3. 使用场景
- 日志上报/埋点发送
- 缓存计算
- 非首屏数据预加载
- DOM 结构分析
- 离线缓存更新
9. scheduler.postTask
scheduler.postTask()
是一个 实验性的浏览器原生 API,属于 Scheduler API 的一部分,用于更智能、更精细地调度任务执行。
它比 setTimeout
、requestIdleCallback
更强大,也更精确地控制任务优先级。
window.scheduler.postTask() 允许开发者将任务加入浏览器的调度队列中,并通过指定优先级(priority)来控制执行顺序。浏览器会跟据空闲情况与优先级智能的调度任务。
9.1. 语法
scheduler.postTask(callback, options)
参数:
- callback: 要执行的函数。
- options: 一个配置对象,常用属性如下:
{priority: 'user-blocking' | 'user-visible' | 'background',signal: AbortSignal // 可选,用于取消任务
}
- 返回值:返回一个 Promise 对象,在任务执行后 resolve。
9.2. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><title>BroadcastChannel Demo</title></head><body><button id="btn">执行任务</button><script>document.getElementById("btn").addEventListener("click", async () => {console.log("主线程任务开始");await scheduler.postTask(() => {console.log("高优先级任务:用户交互相关");},{priority: "user-blocking"});await scheduler.postTask(() => {console.log("中优先级任务:界面更新");},{priority: "user-visible"});await scheduler.postTask(() => {console.log("低优先级任务:后台统计");},{priority: "background"});console.log("主线程任务结束");});</script></body>
</html>
9.3. 取消任务
const controller = new AbortController();scheduler.postTask(() => {console.log('执行前被取消');
}, { signal: controller.signal });controller.abort(); // 任务不会执行
9.4. 兼容性与区别
目前仅 Chrome 94+ / Edge 94+ / Opera 80+ 支持。
Safari 和 Firefox 仍未实现。
setTimeout
:延迟执行任务。
requestIdleCallback
:在浏览器空闲时执行。
scheduler.postTask
:有浏览器智能调度,支持优先级
10. AbortController
AbrotController 用于创建一个取消信号(signal),这个信号可以被多个信号异步操作监听,当你调用 .abrot()
时,这些操作会立即停止。
10.1. 语法
const controller = new AbortController();
const signal = controller.signal;
- contoroller:控制器实例
- signal:控制信号,用来传递“是否被取消”的状态
10.2. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><title>AbortController 请求取消示例</title></head><body><button id="start">开始请求</button><button id="cancel">取消请求</button><script>let controller; // 保存控制器document.getElementById("start").addEventListener("click", () => {controller = new AbortController();const signal = controller.signal;console.log("开始发送请求...");// 这个接口会延迟 5 秒返回fetch("https://deelay.me/5000/https://jsonplaceholder.typicode.com/todos/1", {signal,}).then(res => res.json()).then(data => console.log("请求成功:", data)).catch(err => {if (err.name === "AbortError") {console.warn("请求被中止");} else {console.error("请求错误:", err);}});});document.getElementById("cancel").addEventListener("click", () => {if (controller) {controller.abort();console.log("已调用 abort()");}});</script></body>
</html>
11. ReadableStream
ReadableStream 是浏览器中强大的 原生流式 API(Streaming API),用于按块(chunk)读取数据,而不是一次性加载整个资源
简单来说 ReadableStream 能按块读取网络或文件内容,而不是等到所有内容都下载完
11.1. 为什么需要它
假设你要请求一个 1GB 的视频或超长文本:
- 普通
fetch()
或axios
必须等到 整个响应加载完 才能处理。 - 有了
ReadableStream
,你可以 边下载边处理(比如流式渲染、实时显示日志、逐步解析 JSON 等)。
11.2. 基本语法
const reader = response.body.getReader();
while (true) {const { done, value } = await reader.read();if (done) break;appendChunk(value);
}
概念:
- ReadableStream:表示可读数据流(数据“源头”)
- getReader():获取流的读取器
- read():每次读取一块数据
{ done, value }
- controller.enqueue():向流中添加数据
- controller.close():关闭流
- TextDecoder:把二进制转为字符串
11.3. 代码演示:ai 流式输出
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>AI 流式输出 Demo</title><style>body {font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,Arial, sans-serif;background-color: #121212;color: #e0e0e0;display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;}.container {width: 80%;max-width: 700px;background-color: #1e1e1e;border-radius: 8px;padding: 2rem;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);}h1 {color: #bb86fc;text-align: center;margin-bottom: 2rem;}button {display: block;margin: 0 auto 2rem auto;padding: 10px 20px;font-size: 16px;cursor: pointer;background-color: #03dac6;color: #121212;border: none;border-radius: 4px;transition: background-color 0.3s;}button:hover {background-color: #018786;}#ai-output {background-color: #2c2c2c;border: 1px solid #444;border-radius: 4px;padding: 1rem;min-height: 200px;white-space: pre-wrap; /* 保持换行和空格 */line-height: 1.6;font-family: "Courier New", Courier, monospace;}</style></head><body><div class="container"><h1>AI 流式输出 Demo</h1><button id="startButton">向 AI 提问</button><div id="ai-output"></div></div><script>// --- 1. 获取 DOM 元素 ---const startButton = document.getElementById("startButton");const outputElement = document.getElementById("ai-output");// --- 2. 预先定义好 AI 的完整回答 ---const fullText = `当然!ReadableStream 是一个强大的 Web API,它允许你以数据块(chunks)的形式顺序地处理一个大数据源,而无需一次性将所有数据加载到内存中。它的核心优势在于:
1. **内存高效**:无论源文件有多大,内存占用都极低。
2. **响应迅速**:一旦接收到第一个数据块,就可以立即开始处理,提升用户体验。这对于处理大文件下载、流式音视频播放等场景至关重要。`;// --- 3. 模拟流式输出的函数 ---function simulateAIStream(text, element) {// 清空之前的内容element.textContent = "";// 将完整文本拆分成单个字符的数组const chars = text.split("");let charIndex = 0;// 设置一个定时器,每 50 毫秒追加一个字符const streamInterval = setInterval(() => {// 检查是否还有字符需要显示if (charIndex < chars.length) {// 追加一个字符到元素的 textContent 中element.textContent += chars[charIndex];charIndex++;} else {// 如果所有字符都已显示,清除定时器clearInterval(streamInterval);console.log("流式输出完毕!");}}, 30); // 30毫秒的间隔,可以调整这个值来改变打字速度}// --- 4. 给按钮添加点击事件监听 ---startButton.addEventListener("click", () => {simulateAIStream(fullText, outputElement);});</script></body>
</html>
11.4. 使用场景
- 大文件下载:边下载边显示进度
- AI/chatGPT 流式输出:模拟“字逐个出现”效果
- 实时日志流:实时打印服务器日志
- 流式 JSON 解析:大数据接口不必一次性解析
12. WritableStream
WritableStream 是用于以流的方式写入数据。简单来说它可以逐步接受并处理数据,而不是等整个一次性加载完。
WritableStream
是浏览器原生提供的一个“写入管道”,可以用来流式接收、处理或保存数据(而不是一次性加载完)。
一句话理解:
ReadableStream
是「读流」,
WritableStream
是「写流」。
当要把数据写入文件、网络请求、压缩器、转换器等目标时,就会用到WritableStream
12.1. 语法
const writer = stream.writable.getWriter();
await writer.write(chunk);
概念:
- ReadableStream:“可读流” → 数据来源
- WritableStream:“可写流” → 数据接收
- pipeTo():将可读流连接到可写流
- getWriter():获取写入对象
- write:写入数据块
- close():结束流
- abrot():中断写入
12.2. 代码演示
const stream = new WritableStream({write(chunk) {console.log("写入数据:", chunk);},close() {console.log("写入完成");},abort(err) {console.error("写入被中止:", err);}
});const writer = stream.getWriter();writer.write("Hello");
writer.write("Stream");
writer.write("World");
writer.close();
12.3. 基本结构
new WritableStream({start(controller) {// 初始化时调用},write(chunk, controller) {// 每次写入调用},close(controller) {// 流关闭时调用},abort(reason) {// 流出错或被取消时调用}
})
12.4. 典型应用场景
- 文件写入:使用 File System Access API 进行本地保存
- 大文件上传:分块上传(比如大文件断点续传)
- 数据管道(pipe):与 ReadableStream 结合,实现“流式复制”
- 实时处理数据:边读边写,比如数据转换、压缩、加密
- 逐块写入磁盘或网络
- 实时保存草稿
12.5. 示例:从 ReadableStream 管道写入 WritableStream
const readable = new ReadableStream({start(controller) {["A", "B", "C"].forEach(chunk => controller.enqueue(chunk));controller.close();},
});const writable = new WritableStream({write(chunk) {console.log("接收到数据:", chunk);},close() {console.log("所有数据写入完成");},
});// 管道连接:将 readable 的数据写入 writable
readable.pipeTo(writable);
12.6. 示例:写入文件(File System Access API)
async function saveFile() {// 1. 用户选择保存位置const fileHandle = await window.showSaveFilePicker({suggestedName: "stream-demo.txt"});const writable = await fileHandle.createWritable();// 2. 通过 WritableStream 写入数据await writable.write("第一行\n");await writable.write("第二行\n");await writable.close();console.log("文件已保存!");
}
13. Background Fetch
Background Fetch(后台获取)API 可以让网页在关闭或切后台时,仍然可以继续下载大文件(比如视频、离线包、游戏资源等)。
简单来说:Background Fetch 允许网页通过 Service Worker 在后台下载或上传大型文件,即使用户关闭了页面也不会中断任务
13.1. 为什么需要它
普通的 fetch()
请求在页面关闭后会被中断。
而如果是:
- 要下载 超大视频/压缩包
- 要上传大文件
- 要在后台静默更新离线缓存
那么就需要 Bacnground Fetch。
13.2. 基本使用流程
// 1️. 注册 Service Worker
navigator.serviceWorker.register('/sw.js');// 2️. 在主线程中发起后台下载
const bgFetch = await registration.backgroundFetch.fetch('video-download', // 下载任务的唯一标识['/videos/big-video.mp4'], // 要下载的资源{title: '下载大视频中...',icons: [{ sizes: '72x72', src: '/icon.png', type: 'image/png' }],downloadTotal: 100 * 1024 * 1024 // 可选:预估文件大小(100MB)}
);
然后在 Service Worker 里监听事件
// sw.js
self.addEventListener('backgroundfetchsuccess', event => {console.log('后台下载成功!');// 获取所有下载结果event.waitUntil(async function () {const records = await event.registration.matchAll();for (const record of records) {const response = await record.responseReady;const blob = await response.blob();// 保存文件或缓存console.log('保存文件:', blob.size, '字节');}}());
});self.addEventListener('backgroundfetchfail', event => {console.log('下载失败:', event.registration.id);
});self.addEventListener('backgroundfetchabort', event => {console.log('下载被用户取消');
});
背景获取的事件生命周期 :
- backgroundfetchclick:用户点击通知
- backgroundfetchsuccess:下载全部成功
- backgroundfetchfail:部分或全部失败
- backgroundfetchabrot:用户取消下载
13.3. 应用场景
- 视频下载:用户离开页面时视频仍能继续缓存
- 离线应用更新:后台下载新版本资源包
- 大文件上传:用户切出页面后上传不被中断
- 后台通知:推送通知提醒用户
14. File System Access
File System Acess API 允许网页直接读取、写入和保存用户本地文件(需要用户授权)。这个 API 让 Web 应用拥有类似桌面应用的文件操作能力。
14.1. 语法
const [fh] = await showOpenFilePicker();
editor.value = await (await fh.getFile()).text();
File System Access API 提供了几个核心接口:
- showOpenFilePicker:打开文件选择器,获取文件句柄( FileSystemFileHandle )
- showSaveFilePicker:打开文件保存对话框,获取保存文件的句柄
- showDirectoryPicker:打开目录选择器,获取文件夹句柄( FileSystemDirectoryHandle )
- FileSystemFileHandle:表示一个文件可以读取内容或写入内容
- FileSystemWritableFileStream:写入流,用于将数据写入文件
14.2. 代码演示
14.2.1. 打开文件并读取内容
<button id="open">打开文件</button><pre id="output"></pre><script>document.getElementById("open").addEventListener("click", async () => {try {// 打开文件选择器const [fileHandle] = await window.showOpenFilePicker();const file = await fileHandle.getFile();// 读取内容const content = await file.text();document.getElementById("output").textContent = content;} catch (err) {console.error("读取文件失败:", err);}});
</script>
14.2.2. 写入文件(保存内容)
<button id="save">保存文件</button><script>document.getElementById("save").addEventListener("click", async () => {try {// 打开保存文件对话框const handle = await window.showSaveFilePicker({suggestedName: "example.txt",types: [{description: "Text Files",accept: { "text/plain": [".txt"] },}],});// 创建写入流const writable = await handle.createWritable();await writable.write("这是写入的内容!");await writable.close();alert("文件已保存!");} catch (err) {console.error("写入文件失败:", err);}});
</script>
14.2.3. 读取文件夹中的多个文件
<button id="openDir">选择文件夹</button><script>document.getElementById("openDir").addEventListener("click", async () => {try {const dirHandle = await window.showDirectoryPicker();for await (const [name, handle] of dirHandle.entries()) {if (handle.kind === "file") {const file = await handle.getFile();console.log(`文件名: ${name}, 大小: ${file.size}`);}}} catch (err) {console.error("访问文件夹失败:", err);}});
</script>
14.2.4. 修改文件内容
const [fileHandle] = await showOpenFilePicker();
const writable = await fileHandle.createWritable();
await writable.write("更新文件的新内容");
await writable.close();
14.2.5. 在线文本编辑器
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /><title>文件系统访问 Demo - 在线编辑器</title><style>body {font-family: "Microsoft YaHei", sans-serif;background: #f5f5f5;display: flex;flex-direction: column;align-items: center;padding: 30px;}h1 {color: #333;}.buttons {margin-bottom: 20px;}button {background: #0078d7;color: white;border: none;border-radius: 6px;padding: 10px 20px;margin: 0 5px;cursor: pointer;transition: background 0.3s;}button:hover {background: #005fa3;}textarea {width: 80%;height: 400px;font-size: 16px;padding: 10px;border-radius: 8px;border: 1px solid #ccc;resize: none;outline: none;background: #fff;}</style>
</head>
<body><h1>在线文本编辑器</h1><div class="buttons"><button id="openFile">打开文件</button><button id="saveFile">保存文件</button></div><textarea id="editor" placeholder="在这里编辑文本..."></textarea><script>let fileHandle = null; // 保存当前打开的文件句柄// 打开文件document.getElementById("openFile").addEventListener("click", async () => {try {const [handle] = await window.showOpenFilePicker({types: [{description: "文本文件",accept: { "text/plain": [".txt", ".md", ".json"] },},],});fileHandle = handle;const file = await handle.getFile();const content = await file.text();document.getElementById("editor").value = content;alert(`已打开文件: ${file.name}`);} catch (err) {console.error("打开文件失败:", err);}});// 保存文件document.getElementById("saveFile").addEventListener("click", async () => {try {if (!fileHandle) {// 如果还没有文件句柄,就弹出保存窗口fileHandle = await window.showSaveFilePicker({suggestedName: "new-file.txt",types: [{description: "文本文件",accept: { "text/plain": [".txt", ".md", ".json"] },},],});}const writable = await fileHandle.createWritable();await writable.write(document.getElementById("editor").value);await writable.close();alert("文件已保存!");} catch (err) {console.error("保存文件失败:", err);}});</script>
</body>
</html>
14.3. 总结
打开文件: showOpenFilePicker()
保存文件: showSaveFilePicker()
选择文件夹: showDirectoryPicker()
写文件:handle.createWritable()
读文件:handle.getFile()
15. Clipboard
Clipboard API 是浏览器原生提供的“复制/粘贴”能力。 可以用它 复制文本 / 图片到系统剪贴板,也可以 从剪贴板读取内容。
15.1. 语法
await navigator.clipboard.writeText('要复制的文字') //复制await navigator.clipboard.readText()// 粘贴
15.2. 功能
功能 | 方法 | 说明 |
写入文本 |
| 把字符串复制到剪贴板 |
读取文本 |
| 从剪贴板读取文本内容 |
写入多种类型 |
| 可写入图片、HTML 等 |
读取多种类型 |
| 可读取图片、HTML 等(需要 HTTPS) |
15.3. 代码演示
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><title>Clipboard API Demo</title><style>body {font-family: "Microsoft YaHei", sans-serif;background: #f7f7f7;display: flex;flex-direction: column;align-items: center;padding: 50px;}h1 {color: #333;}textarea {width: 80%;height: 150px;margin: 10px 0;padding: 10px;font-size: 16px;border-radius: 6px;border: 1px solid #ccc;resize: none;outline: none;}button {background-color: #0078d7;color: white;border: none;padding: 10px 20px;border-radius: 6px;margin: 5px;cursor: pointer;transition: 0.2s;}button:hover {background-color: #005fa3;}</style></head><body><h1>Clipboard API Demo</h1><textarea id="inputText" placeholder="输入或粘贴内容..."></textarea><div><button id="copyBtn">复制到剪贴板</button><button id="pasteBtn">从剪贴板粘贴</button></div><script>const input = document.getElementById("inputText");const copyBtn = document.getElementById("copyBtn");const pasteBtn = document.getElementById("pasteBtn");// 复制到剪贴板copyBtn.addEventListener("click", async () => {try {await navigator.clipboard.writeText(input.value);alert("已复制到剪贴板!");} catch (err) {alert("复制失败:" + err);}});// 从剪贴板读取pasteBtn.addEventListener("click", async () => {try {const text = await navigator.clipboard.readText();input.value = text;alert("已从剪贴板读取内容!");} catch (err) {alert("粘贴失败:" + err);}});</script></body>
</html>
16. URLSearchParams
URLSearchParams 用于解析、构建和操作 URL 查询参数 (就是 ?key=value&key2=value2
那部分)
简单理解:URLSearchParams
就是一个可以帮你轻松读取和修改 URL 查询字符串的工具类
16.1. 基本语法
const params = new URLSearchParams('?name=Tom&age=20')
16.2. 常用方法
方法 | 作用 | 示例 |
| 获取参数值 |
|
| 修改或新增参数 |
|
| 添加重复参数 |
|
| 删除参数 |
|
| 判断是否存在 |
|
| 转成查询字符串 |
|
16.3. 代码演示
一句话总结:URLSearchParams
就是专门帮你读、改、拼接 URL 查询字符串的工具,比自己用正则拆字符串安全又简单。
16.3.1. 解析当前 URL 参数
// 当前网址: https://example.com/?user=mingfei&role=admin
const params = new URLSearchParams(window.location.search)console.log(params.get('user')) // mingfei
console.log(params.get('role')) // admin
16.3.2. 动态构建 URL
const params = new URLSearchParams()
params.set('page', 2)
params.set('limit', 10)const url = `https://api.example.com/users?${params.toString()}`
console.log(url)
// 输出: https://api.example.com/users?page=2&limit=10
16.3.3. 遍历所有参数
const params = new URLSearchParams('?a=1&b=2&c=3')for (const [key, value] of params) {console.log(key, value)
}
// 输出:
// a 1
// b 2
// c 3
16.3.4. 支持重复参数(数组场景)
const params = new URLSearchParams()
params.append('tag', 'vue')
params.append('tag', 'javascript')console.log(params.toString())
// tag=vue&tag=javascriptconsole.log(params.getAll('tag'))
// ['vue', 'javascript']
16.3.5. 合并参数
const base = new URLSearchParams('a=1&b=2')
const extra = new URLSearchParams('b=3&c=4')for (const [key, value] of extra) base.set(key, value)console.log(base.toString()) // a=1&b=3&c=4
17. structuredClone
strycturedClone 用来实现深拷贝(deep clone)。
简单来说:structuredClone()
是浏览器原生提供的、用来安全复制任意 JS 对象的深拷贝函数,它支持 Map
、Set
、Date
、ArrayBuffer
等复杂类型。
17.1. 语法
const newObj = structuredClone(originalObj)
- 参数:要克隆的对象
- 返回值:一个全新的、与原对象完全独立的副本
17.2. 代码演示
const user = {name: "Mingfei",info: { age: 22, hobby: ["Vue", "React"] },
}const copy = structuredClone(user)copy.info.age = 30
console.log(user.info.age) // 22 不受影响
这就是深拷贝:嵌套对象也会被完整复制。
17.3. 支持的类型
类型 | 是否支持 |
Object / Array | 是 |
Date | 是 |
RegExp | ❌否(会抛错) |
Map / Set | 是 |
TypedArray | 是 |
ArrayBuffer | 是 |
Blob / File | 是 |
DOM Node | ❌ 否(会报错) |
Function | ❌ 否(会报错) |
17.4. 与 JSON 方式的区别
特性 |
|
|
深拷贝 | ✅ | ✅ |
支持循环引用 | ❌ | ✅ |
支持 | ❌ | ✅ |
支持 | ❌ | ✅ |
丢失函数 | ✅ | ✅ |
性能 | 一般 | 更快且更安全 |
structuredClone()
是现代 JS 官方提供的“万能深拷贝神器”,比 JSON 方案更安全、更快、更全面,支持复杂数据结构和循环引用。
18. Intl.NumberFormat
Intl.numberFormat 是一个非常使用的原生国际化(I18n) API。可以在不同语言、地区下自动格式化数字、货币、百分比等。
简单来说:Intl.NumberFormat
是 JavaScript 内置的国际化格式化工具,用来把数字根据语言与地区规则格式化成符合当地习惯的字符串。
18.1. 语法
const formatter = new Intl.NumberFormat([locales], [options])
formatter.format(number)
locales
:地区/语言代码,如"zh-CN"
、"en-US"
、"ja-JP"
options
:控制格式(货币、百分比、小数位数等)
18.2. 代码演示
18.2.1. 最简单的例子
const num = 1234567.89console.log(new Intl.NumberFormat().format(num))
// 在中文环境中输出:1,234,567.89
自动加上千位分隔符(,
),不同语言下表现不同。
18.2.2. 格式化为货币
const price = 1234.5console.log(new Intl.NumberFormat('zh-CN', {style: 'currency',currency: 'CNY'
}).format(price))
// 输出:¥1,234.50console.log(new Intl.NumberFormat('en-US', {style: 'currency',currency: 'USD'
}).format(price))
// 输出:$1,234.50
根据地区自动加货币符号,currency
支持 USD
、CNY
、JPY
、EUR
等。
18.2.3. 格式化百分比
const ratio = 0.456console.log(new Intl.NumberFormat('zh-CN', {style: 'percent',maximumFractionDigits: 1
}).format(ratio))
// 输出:45.6%
18.2.4. 指定最大小数位数
const num = 1234.567console.log(new Intl.NumberFormat('en-US', {minimumFractionDigits: 2,maximumFractionDigits: 2
}).format(num))
// 输出:1,234.57
18.2.5. 货币与数字混合控制
const salary = 9876543.21const formatter = new Intl.NumberFormat('de-DE', { // 德国style: 'currency',currency: 'EUR',
})console.log(formatter.format(salary))
// 输出:9.876.543,21 €
注意:欧洲习惯用 .
做千分位,,
做小数点。
18.2.6. 紧凑显示(K/M/B)
const views = 1234567console.log(new Intl.NumberFormat('en', {notation: 'compact',compactDisplay: 'short'
}).format(views))
// 输出:1.2M
适合展示粉丝数、播放量、点赞数等场景。
18.3. 常用选项总结表
选项 | 作用 |
|
|
| 设置货币代码(如 |
|
|
|
|
| 最少保留小数位 |
| 最多保留小数位 |
|
|
19. EyeDropper
EyeDropper 可以让用户在浏览器中取色(吸管工具),就像 Photoshop 或 Figma 那样。它是完全零依赖的浏览器原生能力。
19.1. 语法
const eyeDropper = new EyeDropper();eyeDropper.open().then(result => {console.log(result.sRGBHex); // 输出颜色字符串,如 #1a2b3c}).catch(err => {console.error('用户取消或取色失败:', err);});
19.2. 代码演示
<button id="pickColor">取色</button>
<div id="colorBox" style="width:100px; height:100px; border:1px solid #ccc;"></div><script>const btn = document.getElementById('pickColor');const box = document.getElementById('colorBox');btn.addEventListener('click', async () => {if (!window.EyeDropper) {alert('当前浏览器不支持 EyeDropper API');return;}try {const eyeDropper = new EyeDropper();const result = await eyeDropper.open(); // 打开取色工具box.style.backgroundColor = result.sRGBHex; // 显示取到的颜色console.log('取到的颜色:', result.sRGBHex);} catch (err) {console.log('用户取消取色或出错', err);}});
</script>
20. WebCodecs
WebCodes 它能让你直接访问浏览器底层的视频/音频编解码器(Codec),无需 ffmpeg、无需插件。
20.1. WebCOdecs 是什么
WebCodecs API
是一个底层多媒体编解码接口,允许 JavaScript 高效地:
- 解码(decode) 视频或音频帧(比如 mp4、webm)
- 编码(encode) 视频或音频帧(比如生成 webm 文件)
- 在不经过
<video>
或<audio>
的情况下处理媒体数据
它为浏览器多媒体处理提供了近乎原生的性能。
20.2. 语法
const decoder = new VideoDecoder({output: frame => ctx.drawImage(frame, 0, 0),error: console.error
});
decoder.configure({ codec: 'vp09.00.10.08' });
20.3. 基本组成
WebCodecs
主要由以下几个核心类组成:
类名 | 功能 |
| 将压缩视频数据(如 H.264)解码为 |
| 将 编码为压缩格式 |
| 解码音频 |
| 编码音频 |
| 视频帧对象,可直接绘制到 |
| 压缩后的视频片段 |
20.4. 代码演示
20.4.1. 最基本的使用示例:解码视频帧
// 1️. 创建视频解码器
const decoder = new VideoDecoder({output: handleFrame, // 每当解码出一帧时触发error: (e) => console.error("解码错误:", e)
});// 2️. 配置解码器
decoder.configure({codec: 'vp8', // 支持: vp8, vp9, avc1 (H.264), av01, 等
});// 3️. 模拟输入压缩视频数据
const chunk = new EncodedVideoChunk({type: "key", // 关键帧或 delta 帧timestamp: 0,data: new Uint8Array([/* 视频数据字节流 */])
});// 4️. 输入视频数据进行解码
decoder.decode(chunk);// 5️. 每帧输出回调
function handleFrame(frame) {console.log("解码到视频帧:", frame);// 可以直接绘制到 canvasconst canvas = document.querySelector("canvas");const ctx = canvas.getContext("2d");ctx.drawImage(frame, 0, 0);frame.close(); // 必须手动释放内存
}
20.4.2. 编码示例:生成视频数据
const encoder = new VideoEncoder({output: (chunk) => console.log("编码完成片段:", chunk),error: (e) => console.error(e),
});encoder.configure({codec: 'vp8',width: 640,height: 480,bitrate: 1_000_000,framerate: 30,
});// 用 Canvas 创建一帧
const canvas = document.createElement("canvas");
canvas.width = 640;
canvas.height = 480;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "skyblue";
ctx.fillRect(0, 0, 640, 480);// 将 Canvas 转成 VideoFrame
const frame = new VideoFrame(canvas, { timestamp: 0 });// 编码该帧
encoder.encode(frame);
frame.close();
20.5. 性能优势
- 直接使用系统硬件编解码器(GPU 加速);
- 无需引入庞大的 ffmpeg.js;
- 延迟极低,可用于实时视频流、虚拟会议、游戏录制等。
20.6. 应用场景
- 视频会议:低延迟推流、实时解码。
- 在线录屏:实时采集和编码
- 视频编辑器:本地转码、剪辑
- WebRTC 优化:自定义流处理前后端传输
- AI 视频分析:解码帧后传入 WebGPU/WebGL