实用 JS 原生 API 学习笔记
约 2543 字大约 8 分钟
2025-10-23
ResizeObserver 监听元素尺寸
监听元素尺寸变化(不是窗口 resize),解决图表自适应、动态布局、虚拟滚动等场景。
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);DEMO(自动改变尺寸时调整字体)
<div id="box" style="resize:both;overflow:auto;border:1px solid #333;width:200px;height:100px;">
调整我尺寸试试
</div>
<script>
const box = document.getElementById('box');
new ResizeObserver(([entry]) => {
box.style.fontSize = entry.contentRect.width / 20 + 'px';
}).observe(box);
</script>使用场景
- 图表容器自适应(ECharts、G2)
- CSS Grid/Flex 导致布局重新计算
- 富文本编辑器自适应
高级用法
- 监听
contentBoxSize和borderBoxSize获取更精确度量。 - 与
requestAnimationFrame或debounce结合,避免回调频繁触发导致布局抖动。
生产实践
- 对回调内部涉及 DOM 修改的操作做节流/防抖。
- 在组件卸载时调用
observer.disconnect()释放资源。
常见坑
- 在回调里修改被观察元素尺寸可能造成循环触发。
- 有些旧版本 Safari/Firefox 行为差异,用
entry.contentRect做兼容。
兼容性 & polyfill
- 支持:Chrome 64+、Firefox 69+、Safari 13.1+。
- Polyfill:若需支持旧浏览器,可用第三方 polyfill(此处给出简单替代思路:在没有原生时退回到
window.resize+ 轮询检测元素尺寸的实现)。
IntersectionObserver 可视区域监听
懒加载 / 曝光埋点
用途 & 痛点
- 替代滚动事件进行元素进入/离开视口的检测,极大减少帧率损耗。
- 场景:图片懒加载、广告曝光埋点、自动播放视频。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>IntersectionObserver Demo</title>
</head>
<body style="min-height: 200vh">
<img
id="img"
data-src="https://avatars.githubusercontent.com/u/171486473?v=4"
alt="lazy"
style="
display: block;
margin-top: 800px;
width: 600px;
height: 200px;
background: #eee;
"
/>
<script>
const img = document.getElementById("img");
const io = new IntersectionObserver(
(entries, obs) => {
const e = entries[0];
if (e.isIntersecting) {
img.src = img.dataset.src;
obs.disconnect();
}
},
{ threshold: 0.1, rootMargin: "100px 0px" }
);
io.observe(img);
</script>
</body>
</html>高级用法
rootMargin做预加载(提前触发)。- 通过
threshold数组获取进入比例的精细控制。
生产实践
- 批量观察:为多个元素创建一个
IntersectionObserver实例,避免为每个元素创建实例带来的开销。 - 在曝光埋点中要防重发(使用
once语义)。
兼容性 & polyfill
- 支持:Chrome 51+、Firefox 55+、Safari 12.1+。
- Polyfill:可用
intersection-observernpm 包(如果需支持旧内核)。
Page Visibility 页面可见性检测
用途 & 痛点
监听页面是否可见(tab 切换、最小化),用于暂停动画、降低轮询频率、节省带宽。
<!DOCTYPE html>
<html>
<meta charset="UTF-8" />
<body>
<script>
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
console.log("页面进入后台:暂停轮询/动画");
// pause tasks
} else {
console.log("页面回到前台:恢复");
}
});
</script>
</body>
</html>高级用法
- 与
Wake Lock或requestIdleCallback联动,按需调整行为。
兼容性
- 广泛支持,但有些旧浏览器的
visibilityState值略有差异。
Web Share 原生系统分享
用途
调用系统分享面板,体验更统一(H5 分享推荐)。
<!DOCTYPE html>
<html>
<meta charset="UTF-8" />
<body>
<button id="share">Share</button>
<script>
const btn = document.getElementById("share");
btn.addEventListener("click", async () => {
if (navigator.share) {
try {
await navigator.share({
title: document.title,
text: "Awesome",
url: location.href,
});
console.log("shared");
} catch (e) {
console.error(e);
}
} else alert("不支持 Web Share");
});
</script>
</body>
</html>注意
- 需 HTTPS(或 localhost),且必须在用户手势触发下调用。
- 桌面端支持不尽相同。
Wake Lock 防止设备息屏
用途
在直播、演示或导航场景保持屏幕常亮。
<!DOCTYPE html>
<html>
<meta charset="UTF-8" />
<body>
<button id="lock">保持常亮</button>
<script>
let lock;
const btn = document.getElementById("lock");
btn.addEventListener("click", async () => {
try {
if ("wakeLock" in navigator) {
lock = await navigator.wakeLock.request("screen");
lock.addEventListener("release", () =>
console.log("lock released")
);
console.log("acquired");
} else {
alert("不支持 Wake Lock");
}
} catch (e) {
console.error(e);
}
});
</script>
</body>
</html>生产提示
- 页面可见性变化会导致锁被释放,需要在
visibilitychange时重新申请。
兼容性
- 新 API,部分浏览器支持(主要 Chromium 系列),老浏览器需优雅降级。
BroadcastChannel 同源标签页通信
用途
在同一源的多个标签页之间传递消息(登录态同步、主题切换、协作文档协同)。
<!DOCTYPE html>
<html>
<meta charset="UTF-8" />
<body>
<button id="send">发送消息</button>
<script>
const ch = new BroadcastChannel("app-channel");
ch.onmessage = (e) => console.log("收到:", e.data);
document.getElementById("send").onclick = () =>
ch.postMessage({ time: Date.now(), text: "hello from tab" });
</script>
</body>
</html>降级
- 跨域或老浏览器可用
localStorage+storage事件作为替代(延迟、频宽较差)。
PerformanceObserve 性能指标采集
用途
监听浏览器性能入口(paint、resource、longtask、layout-shift 等),无侵入采集 FCP/LCP/FID 等指标。
<!DOCTYPE html>
<html>
<body>
<script>
if ("PerformanceObserver" in window) {
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log("perf entry", entry.type || entry.name, entry);
}
});
po.observe({ type: "paint", buffered: true });
// 还能观察 'largest-contentful-paint', 'layout-shift' 等
}
</script>
</body>
</html>生产实践
- 将采集结果分批发送到后端,避免每条都发送。
requestIdleCallback 浏览器空闲时执行任务
用途
在主线程空闲时执行非关键任务(埋点、预加载、缓存清理),减少对关键渲染路径的影响。
<!DOCTYPE html>
<html>
<body>
<script>
const task = (deadline) => {
while (deadline.timeRemaining() > 0 && tasks.length) {
const t = tasks.shift();
t();
}
if (tasks.length) requestIdleCallback(task, { timeout: 1000 });
};
const tasks = Array.from({ length: 5 }).map(
(_, i) => () => console.log("task", i)
);
requestIdleCallback(task);
</script>
</body>
</html>兼容性
一些浏览器未实现,可 polyfill(用 setTimeout(fn,0) + time budget 模拟)。
scheduler.postTask 原生任务优先级队列
用途
提供与平台调度器交互的 API,提交带优先级的低开销任务(尚在部分浏览器实验阶段)。
<!DOCTYPE html>
<html>
<body>
<script>
if (window.scheduler && scheduler.postTask) {
scheduler.postTask(() => console.log("background task"), {
priority: "background",
});
} else {
// 降级:用 requestIdleCallback 或 setTimeout
requestIdleCallback(() => console.log("bg task fallback"));
}
</script>
</body>
</html>注意:API 仍在演进,使用前应 feature-detect。
AbortController 取消异步任务(fetch、stream)
用途
取消 fetch 请求、ReadableStream、scheduler.postTask 等,解决竞态、切换时撤销请求。
<!DOCTYPE html>
<html>
<body>
<button id="start">Start Fetch</button>
<button id="cancel">Cancel</button>
<script>
let ctrl;
document.getElementById("start").onclick = async () => {
ctrl = new AbortController();
try {
const r = await fetch("https://httpbin.org/delay/5", {
signal: ctrl.signal,
});
console.log("ok", await r.text());
} catch (e) {
console.error("fetch error", e.name);
}
};
document.getElementById("cancel").onclick = () => ctrl?.abort();
</script>
</body>
</html>高级
AbortSignal.timeout(ms)可创建自动超时信号(某些环境支持)。
ReadableStream 流式读取(大文件下载)
用途
- 分块读取 response.body,适用于大文件或边下边写场景,避免一次性占用大量内存。
示例(分块下载并显示进度)
<!DOCTYPE html>
<html>
<body>
<meta charset="utf-8" />
<button id="dl">下载并显示进度</button>
<progress id="p" max="100" value="0" style="width: 80%"></progress>
<script>
document.getElementById("dl").onclick = async () => {
// 下载地址可能已经不可用
const res = await fetch("https://nbg1-speed.hetzner.com/1GB.bin");
const len = +res.headers.get("Content-Length");
const reader = res.body.getReader();
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
received += value.length;
document.getElementById("p").value = (received / len) * 100;
}
alert("done");
};
</script>
</body>
</html>注意:小文件不必用流,流更适合大文件或需要逐步处理的情形。
WritableStream 流式写入(上传 / 本地写)
用途
构建逐块上传或写入的能力,与 ReadableStream 配合用于大文件上传。
<!DOCTYPE html>
<html>
<body>
<meta charset="utf-8" />
<button id="w">模拟写入</button>
<script>
const myWritable = new WritableStream({
write(chunk) {
console.log("写入 chunk", chunk);
},
close() {
console.log("closed");
},
});
const writer = myWritable.getWriter();
document.getElementById("w").onclick = async () => {
await writer.write("hello");
await writer.write("world");
await writer.close();
};
</script>
</body>
</html>生产实践
与服务端 Transfer-Encoding: chunked 联动,服务端需支持流式接收。
Background Fetch PWA 后台下载(断点续传)
用途:Service Worker 中在后台可靠下载大文件,系统通知栏显示进度,适合 PWA 场景。
示例:该 API 需要 Service Worker 和浏览器支持(示例略)。
降级:若不支持,采用后台 fetch + ReadableStream + IndexedDB 存储断点续传数据。
File System Access 浏览器读写本地文件
用途:允许网页直接读写用户授权的本地文件(适用于 Web IDE、画图工具、备份)
<!DOCTYPE html>
<html>
<body>
<meta charset="utf-8" />
<button id="save">保存文件</button>
<script>
document.getElementById("save").onclick = async () => {
if (!window.showSaveFilePicker) return alert("API 不支持");
const handle = await window.showSaveFilePicker({
suggestedName: "hello.txt",
});
const writable = await handle.createWritable();
await writable.write("Hello from browser file API");
await writable.close();
alert("保存完成");
};
</script>
</body>
</html>权限:需要用户交互触发,HTTPS 下可用。
Clipboard API 异步剪贴板
用途:读取/写入剪贴板文本与图片(更强大且异步),替代 execCommand。
<!DOCTYPE html>
<html>
<body>
<meta charset="utf-8" />
<button id="copy">复制</button>
<button id="paste">粘贴</button>
<script>
document.getElementById("copy").onclick = async () => {
try {
await navigator.clipboard.writeText("Hello 2025");
alert("已复制");
} catch (e) {
alert("复制失败");
}
};
document.getElementById("paste").onclick = async () => {
try {
const t = await navigator.clipboard.readText();
alert("剪贴板:" + t);
} catch (e) {
alert("读取失败");
}
};
</script>
</body>
</html>注意:读取剪贴板需要权限与用户手势,图片读写涉及更高权限。
URLSearchParams 方便的查询参数处理
用途:构建/解析查询字符串,替代手写拼接。
<!DOCTYPE html>
<html>
<body>
<meta charset="utf-8" />
<script>
const params = new URLSearchParams({ q: "Web", page: 2 });
console.log(params.toString());
// 解析
const p2 = new URLSearchParams("?a=1&b=2");
console.log([...p2.entries()]);
</script>
</body>
</html>高级:支持重复键、可迭代,方便与 fetch 参数构建结合。
structuredClone 深拷贝(支持复杂类型)
用途:比 JSON.parse(JSON.stringify(...)) 更强,能拷贝 Map/Set/Date/Blob 等,且支持循环引用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>structuredClone Demo</title>
</head>
<body>
<h2>structuredClone Demo</h2>
<button id="run">运行 Demo</button>
<script>
document.getElementById('run').onclick = () => {
// 循环引用示例
const obj = { name: "Alice" };
obj.self = obj; // 循环引用
const copy = structuredClone(obj);
console.log("循环引用拷贝:", copy);
console.log("copy.self === copy ?", copy.self === copy); // true
// 高级示例:Map, Set, Blob
const data = {
map: new Map([["a", 1], ["b", 2]]),
set: new Set([1, 2, 3]),
blob: new Blob(["Hello structuredClone"], { type: "text/plain" })
};
const clonedData = structuredClone(data);
console.log("Map 拷贝:", clonedData.map.get("a")); // 1
console.log("Set 拷贝:", clonedData.set.has(2)); // true
clonedData.blob.text().then(text => console.log("Blob 内容:", text));
alert("查看控制台输出结果");
};
</script>
</body>
</html>浏览器支持
| 浏览器 | 最低支持版本 | 说明 |
|---|---|---|
| Chrome | 98+ | 完整支持,包括 Map/Set/Blob/循环引用 |
| Edge | 98+ | 同 Chrome 内核 |
| Firefox | 94+ | 完整支持 |
| Safari | 15.4+ | 支持大部分类型,但 Safari 旧版本不支持 |
| Opera | 84+ | 基于 Chromium,支持 |
| Node.js | 17+ | 内置支持,低版本需 polyfill |
限制/坑
- 不支持函数:
structuredClone({ fn(){} })会报错。 - 不支持 DOM 节点:无法 clone HTML 元素或节点。
- Class 实例:克隆后会丢失原型,只保留普通对象属性。
- Proxy:无法克隆代理对象。
兼容方案(Polyfill)
对于不支持 structuredClone 的环境,可以降级使用:
if (!window.structuredClone) {
window.structuredClone = obj => JSON.parse(JSON.stringify(obj));
}注意:这种 Polyfill 不支持循环引用、Map、Set、Blob 等复杂类型,仅适合简单对象深拷贝。
总结:现代主流浏览器和 Node.js 17+ 都已经支持 structuredClone,可以放心在新项目中使用,只要注意上述限制。