1. ResizeObserver
ResizeObserver
用于监听元素的尺寸变化。
简单来说:它能在元素的宽高变化时(包括内容撑开、CSS 改变、父容器变化等)自动触发回调。
1.1. 语法
javascript
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. 代码演示
html
<!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. 语法
javascript
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. 代码演示
html
<!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. 语法
javascript
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
console.log('页面隐藏了!')
} else {
console.log('页面可见了!')
}
})
取值如下:
- visible:页面当前可见(在前台)
- hidden:页面不可见(被最小化或切换到其他标签页)
- prerender:页面正在预渲染。
3.2. 代码演示
html
<!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. 语法
javascript
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()
的参数是一个对象,可以包含以下字段:
|---------|----------|------------------------|
| 字段 | 类型 | 说明 |
| title
| string | 分享标题 |
| text
| string | 分享文本 |
| url
| string | 要分享的链接 |
| files
| File[] | 可选,支持分享图片/视频文件(需HTTPS) |
4.2. 代码演示
html
<!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. 代码演示
html
<!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. 代码演示
html
<!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 均可共享 |
| 同源限制 | 只能在相同协议 + 域名 + 端口的上下文间通信 |
| 不能跨浏览器 | 只在当前浏览器进程有效(不同浏览器或隐私模式之间不共享) |
| 生命周期短 | 页面关闭或 close()
后会自动失效 |
6.3. 应用场景
- 多标签页同步登陆状态:登陆或退出后,其他标签自动同步状态
- 多窗口聊天::多页面共享聊天内容
- 多标签页操作同步:设置项、主题切换实时同步
- 与 Web Worker 通信:主线程和 Worker 间广播状态变化
7. PerformanceObserver
PerformanceObserver
用于监听性能事件(performance entries),这些是浏览器在运行过程中自动记录的,比如:
- 页面加载阶段的指标(如 navigation)
- 资源加载(如 resource)
- 长任务(如 longtask)
- 渲染阶段(如 paint)
简单理解:它是浏览器提供的**性能监听器,**可以实时获取数据而不需要手动打断点
7.1. 语法
javascript
// 创建一个性能观察者
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. 常见可观察类型
|------------------------------|-------------------------------|---------------------------------------------------------|
| 类型名 | 说明 | 示例字段 |
| 'resource'
| 外部资源加载性能(CSS、JS、图片等) | entry.name
、duration
、initiatorType
|
| 'navigation'
| 页面加载性能(HTML 解析、DNS、TCP、DOM 等) | domContentLoadedEventEnd
、loadEventEnd
|
| 'paint'
| 渲染阶段性能(首次绘制 FP、首次内容绘制 FCP) | entry.name
= 'first-paint' / 'first-contentful-paint' |
| 'longtask'
| 长任务检测(主线程卡顿 >50ms) | entry.duration
|
| 'largest-contentful-paint'
| 最大内容绘制(LCP) | 用于 Core Web Vitals |
| 'layout-shift'
| 布局偏移(CLS) | entry.value
表示偏移量 |
7.3. 代码演示
html
<!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. 基本语法
javascript
const handle = requestIdleCallback(callback[, options])
cancelIdleCallback(handle)
参数解释:
- callback(deadline):空闲时执行的函数,接收一个 deadline 参数
- options.timeout:超时时间(如果太久没有空闲出来也强制执行)
javascript
requestIdleCallback(myTask, { timeout: 2000 })
deadline 对象解析
- timeRemaining:返回当前帧剩余的空闲时间(毫秒)
- dedTimeout:是否因超时被强制执行
8.2. 代码演示
html
<!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. 语法
javascript
scheduler.postTask(callback, options)
参数:
- callback: 要执行的函数。
- options: 一个配置对象,常用属性如下:
javascript
{
priority: 'user-blocking' | 'user-visible' | 'background',
signal: AbortSignal // 可选,用于取消任务
}
- 返回值:返回一个 Promise 对象,在任务执行后 resolve。
9.2. 代码演示
html
<!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. 取消任务
javascript
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. 语法
javascript
const controller = new AbortController();
const signal = controller.signal;
- contoroller:控制器实例
- signal:控制信号,用来传递"是否被取消"的状态
10.2. 代码演示
html
<!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. 基本语法
javascript
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 流式输出
html
<!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. 语法
javascript
const writer = stream.writable.getWriter();
await writer.write(chunk);
概念:
- ReadableStream:"可读流" → 数据来源
- WritableStream:"可写流" → 数据接收
- pipeTo():将可读流连接到可写流
- getWriter():获取写入对象
- write:写入数据块
- close():结束流
- abrot():中断写入
12.2. 代码演示
javascript
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. 基本结构
javascript
new WritableStream({
start(controller) {
// 初始化时调用
},
write(chunk, controller) {
// 每次写入调用
},
close(controller) {
// 流关闭时调用
},
abort(reason) {
// 流出错或被取消时调用
}
})
12.4. 典型应用场景
- 文件写入:使用 File System Access API 进行本地保存
- 大文件上传:分块上传(比如大文件断点续传)
- 数据管道(pipe):与 ReadableStream 结合,实现"流式复制"
- 实时处理数据:边读边写,比如数据转换、压缩、加密
- 逐块写入磁盘或网络
- 实时保存草稿
12.5. 示例:从 ReadableStream 管道写入 WritableStream
javascript
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)
javascript
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. 基本使用流程
javascript
// 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 里监听事件
javascript
// 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. 语法
javascript
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. 打开文件并读取内容
html
<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. 写入文件(保存内容)
html
<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. 读取文件夹中的多个文件
html
<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. 修改文件内容
javascript
const [fileHandle] = await showOpenFilePicker();
const writable = await fileHandle.createWritable();
await writable.write("更新文件的新内容");
await writable.close();
14.2.5. 在线文本编辑器
html
<!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. 语法
javascript
await navigator.clipboard.writeText('要复制的文字') //复制
await navigator.clipboard.readText()// 粘贴
15.2. 功能
|--------|---------------------------------------|------------------------|
| 功能 | 方法 | 说明 |
| 写入文本 | navigator.clipboard.writeText(text)
| 把字符串复制到剪贴板 |
| 读取文本 | navigator.clipboard.readText()
| 从剪贴板读取文本内容 |
| 写入多种类型 | navigator.clipboard.write(items)
| 可写入图片、HTML 等 |
| 读取多种类型 | navigator.clipboard.read()
| 可读取图片、HTML 等(需要 HTTPS) |
15.3. 代码演示
html
<!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. 基本语法
javascript
const params = new URLSearchParams('?name=Tom&age=20')
16.2. 常用方法
|----------------------|---------|-----------------------------------------------|
| 方法 | 作用 | 示例 |
| get(key)
| 获取参数值 | params.get('name') // "Tom"
|
| set(key, value)
| 修改或新增参数 | params.set('age', 25)
|
| append(key, value)
| 添加重复参数 | params.append('hobby', 'music')
|
| delete(key)
| 删除参数 | params.delete('age')
|
| has(key)
| 判断是否存在 | params.has('name') // true
|
| toString()
| 转成查询字符串 | params.toString() // "name=Tom&hobby=music"
|
16.3. 代码演示
一句话总结:URLSearchParams
就是专门帮你读、改、拼接 URL 查询字符串的工具,比自己用正则拆字符串安全又简单。
16.3.1. 解析当前 URL 参数
javascript
// 当前网址: 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
javascript
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. 遍历所有参数
javascript
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. 支持重复参数(数组场景)
javascript
const params = new URLSearchParams()
params.append('tag', 'vue')
params.append('tag', 'javascript')
console.log(params.toString())
// tag=vue&tag=javascript
console.log(params.getAll('tag'))
// ['vue', 'javascript']
16.3.5. 合并参数
javascript
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. 语法
javascript
const newObj = structuredClone(originalObj)
- 参数:要克隆的对象
- 返回值:一个全新的、与原对象完全独立的副本
17.2. 代码演示
javascript
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 方式的区别
|-------------------------|-----------------------------------|------------------------|
| 特性 | JSON.parse(JSON.stringify(obj))
| structuredClone(obj)
|
| 深拷贝 | ✅ | ✅ |
| 支持循环引用 | ❌ | ✅ |
| 支持 Map
、Set
| ❌ | ✅ |
| 支持 Date
、ArrayBuffer
| ❌ | ✅ |
| 丢失函数 | ✅ | ✅ |
| 性能 | 一般 | 更快且更安全 |
structuredClone()
是现代 JS 官方提供的"万能深拷贝神器",比 JSON 方案更安全、更快、更全面,支持复杂数据结构和循环引用。
18. Intl.NumberFormat
Intl.numberFormat 是一个非常使用的原生国际化(I18n) API。可以在不同语言、地区下自动格式化数字、货币、百分比等。
简单来说:Intl.NumberFormat 是 JavaScript 内置的国际化格式化工具,用来把数字根据语言与地区规则格式化成符合当地习惯的字符串。
18.1. 语法
javascript
const formatter = new Intl.NumberFormat([locales], [options])
formatter.format(number)
locales
:地区/语言代码,如"zh-CN"
、"en-US"
、"ja-JP"
options
:控制格式(货币、百分比、小数位数等)
18.2. 代码演示
18.2.1. 最简单的例子
javascript
const num = 1234567.89
console.log(new Intl.NumberFormat().format(num))
// 在中文环境中输出:1,234,567.89
自动加上千位分隔符(,
),不同语言下表现不同。
18.2.2. 格式化为货币
javascript
const price = 1234.5
console.log(new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
}).format(price))
// 输出:¥1,234.50
console.log(new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price))
// 输出:$1,234.50
根据地区自动加货币符号,currency
支持 USD
、CNY
、JPY
、EUR
等。
18.2.3. 格式化百分比
javascript
const ratio = 0.456
console.log(new Intl.NumberFormat('zh-CN', {
style: 'percent',
maximumFractionDigits: 1
}).format(ratio))
// 输出:45.6%
18.2.4. 指定最大小数位数
javascript
const num = 1234.567
console.log(new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(num))
// 输出:1,234.57
18.2.5. 货币与数字混合控制
javascript
const salary = 9876543.21
const 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)
javascript
const views = 1234567
console.log(new Intl.NumberFormat('en', {
notation: 'compact',
compactDisplay: 'short'
}).format(views))
// 输出:1.2M
适合展示粉丝数、播放量、点赞数等场景。
18.3. 常用选项总结表
|-------------------------|------------------------------------------------------------|
| 选项 | 作用 |
| style
| 'decimal'
/ 'currency
/ 'percent'
/ 'unit'
|
| currency
| 设置货币代码(如 'CNY'
、'USD'
) |
| notation
| 'standard'
/ 'scientific'
/ 'compact'
|
| compactDisplay
| 'short'
/ 'long'
(配合 compact
使用) |
| minimumFractionDigits
| 最少保留小数位 |
| maximumFractionDigits
| 最多保留小数位 |
| unit
| 'kilometer'
/ 'liter'
/ 'hour'
等(style 必须是 'unit'
) |
19. EyeDropper
EyeDropper 可以让用户在浏览器中取色(吸管工具) ,就像 Photoshop 或 Figma 那样。它是完全零依赖的浏览器原生能力。
19.1. 语法
javascript
const eyeDropper = new EyeDropper();
eyeDropper.open()
.then(result => {
console.log(result.sRGBHex); // 输出颜色字符串,如 #1a2b3c
})
.catch(err => {
console.error('用户取消或取色失败:', err);
});
19.2. 代码演示
html
<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 文件)
- 在不经过 **
它为浏览器多媒体处理提供了近乎原生的性能。
20.2. 语法
javascript
const decoder = new VideoDecoder({
output: frame => ctx.drawImage(frame, 0, 0),
error: console.error
});
decoder.configure({ codec: 'vp09.00.10.08' });
20.3. 基本组成
WebCodecs
主要由以下几个核心类组成:
|---------------------|----------------------------------|
| 类名 | 功能 |
| VideoDecoder
| 将压缩视频数据(如 H.264)解码为 VideoFrame
|
| VideoEncoder
| 将 VideoFrame
编码为压缩格式 |
| AudioDecoder
| 解码音频 |
| AudioEncoder
| 编码音频 |
| VideoFrame
| 视频帧对象,可直接绘制到 <canvas>
|
| EncodedVideoChunk
| 压缩后的视频片段 |
20.4. 代码演示
20.4.1. 最基本的使用示例:解码视频帧
javascript
// 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);
// 可以直接绘制到 canvas
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.drawImage(frame, 0, 0);
frame.close(); // 必须手动释放内存
}
20.4.2. 编码示例:生成视频数据
javascript
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