# JS 代码技巧 vol.7 — 20 个浏览器 API 实战,自带 API 能干的事别自己封装

JS 代码技巧 vol.7 --- 20 个浏览器 API 实战,自带 API 能干的事别自己封装

小不的代码技巧系列第 7 期 主题:浏览器 API 实战 这次盘 20 个------每个都对比"老路子自己封" vs "现代浏览器自带 API",附兼容性表,看完就知道啥时候该用啥 😎 20 个技巧一次打包,分网络、监听、剪贴板/分享、存储、性能、UI/DOM 六组。

哈喽哇!我是小不不简说的不~

作为一个在代码界"翻车"无数次的选手,我算是看明白了:坑这东西吧,要么不踩,踩就踩大的😂

今天这期,主打一个 浏览器 API 实战------不整虚的,全是实战里哭出来的教训。看不看随你~反正翻车实录又不收钱

Ps:文末有惊喜(不是广告!)


一、网络请求(4 个)

1. fetch vs XMLHttpRequest

老路子:XHR 一堆回调地狱,abort 要自己写。

js 复制代码
// ❌ 老路子:XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
};
xhr.open("GET", url);
xhr.send();

现代:fetch + Promise,链式清爽。

js 复制代码
const res = await fetch(url);
const data = await res.json();

兼容:Chrome 42+ / Firefox 39+ / Safari 10.1+(2017+),Node 18+ 原生支持。

质疑 :如果说 axios,或者第三方库 ? 那么请仔细阅读 axios 文档

坑在哪 :fetch 默认不带 cookie (要 credentials: 'include'),404 不会 reject (要 res.ok 判断)。老 IE 直接不支持------要 XHR 或 polyfill。


2. AbortController vs 自己写 cancel 标志

老路子:自己定义 cancelled flag,函数里到处检查。

js 复制代码
// ❌ 老路子
let cancelled = false;
async function fetchData() {
  for (const id of ids) {
    if (cancelled) return;
    await fetch(`/api/${id}`);
  }
}
fetchData();
// 想取消?fetchData.cancel = () => cancelled = true;

现代:浏览器自带 AbortController,fetch 原生支持。

js 复制代码
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
  const res = await fetch(url, { signal: controller.signal });
} catch (e) {
  if (e.name === "AbortError") console.log("主动取消");
}

兼容:Chrome 66+ / Firefox 57+ / Safari 11.1+(2018+),Node 15+。

坑在哪 :不是所有 API 都支持 signal(axios 老版本要适配器)。React 组件 unmount 时记得 abort,否则 setState 警告满天飞。


老路子:用户关闭页面前,发个请求记录行为------但 fetch / XHR 在 unload 时经常被浏览器掐断。

js 复制代码
// ❌ fetch 在 unload 时经常被砍
window.addEventListener("unload", () => {
  fetch("/log", { method: "POST", body: JSON.stringify(data) });
});

现代:sendBeacon 是浏览器专给"埋点/日志"设计的,保证送达

js 复制代码
window.addEventListener("unload", () => {
  navigator.sendBeacon("/log", JSON.stringify(data));
});

兼容:Chrome 39+ / Firefox 31+ / Safari 11.1+(2018+)。

坑在哪只能发 POST,body 限制 64KB 。要拿响应?看 fetch 而不是 sendBeacon


4. URL / URLSearchParams vs 正则解析 query

老路子:手写正则拆 query,或者 split('&')

js 复制代码
// ❌ 老路子
function getQuery(name) {
  const m = location.search.match(new RegExp(`[?&]${name}=([^&]*)`));
  return m ? decodeURIComponent(m[1]) : null;
}

现代:浏览器自带 URLSearchParams,开箱即用。

js 复制代码
const params = new URLSearchParams(location.search);
params.get("name"); // '小不'
params.getAll("tag"); // ['js', 'ts']
params.toString(); // 反向序列化

兼容:Chrome 49+ / Firefox 44+ / Safari 10+(2016+),Node 10+。

坑在哪 :URL 含 hash 时 URLSearchParams 不解析 hash 后的内容。需要完整 URL 对象用 new URL(href)


二、元素监听(4 个)

5. IntersectionObserver vs scroll + getBoundingClientRect

老路子:scroll 事件 + 手动算位置判断元素是否在视口。

js 复制代码
// ❌ 老路子:每滚一下都跑回调,性能差
window.addEventListener("scroll", () => {
  imgs.forEach((img) => {
    const rect = img.getBoundingClientRect();
    if (rect.top < window.innerHeight) img.src = img.dataset.src;
  });
});

现代:浏览器自己算交叉,事件触发时机由浏览器决定,性能高。

js 复制代码
const io = new IntersectionObserver((entries) => {
  entries.forEach((e) => {
    if (e.isIntersecting) {
      e.target.src = e.target.dataset.src;
      io.unobserve(e.target);
    }
  });
});
imgs.forEach((img) => io.observe(img));

兼容:Chrome 51+ / Firefox 55+ / Safari 12.1+(2017+)。

坑在哪:iframe 内有时不准确;首屏图片别懒加载(拖慢 LCP)。


6. ResizeObserver vs window resize

老路子:监听 window resize 事件,手动判断元素尺寸变化。

js 复制代码
// ❌ 老路子
window.addEventListener("resize", () => {
  const w = box.offsetWidth;
  if (w !== lastW) doSomething(w);
});

现代:观察任意元素 尺寸变化,窗口不变元素变也能监听到。

js 复制代码
const ro = new ResizeObserver((entries) => {
  for (const e of entries) {
    console.log(e.contentRect.width, e.contentRect.height);
  }
});
ro.observe(box);

兼容:Chrome 64+ / Firefox 69+ / Safari 13.1+(2019+)。

坑在哪 :回调触发频率可能很高,内部用 rAF 节流或自己 debounce。


7. MutationObserver vs DOM Mutation Events

老路子:DOMNodeInserted / DOMAttrModified 一类事件------已废弃,性能还差

js 复制代码
// ❌ 老路子:DOMNodeInserted 已废弃
container.addEventListener("DOMNodeInserted", (e) => {
  console.log("新节点:", e.target);
});

现代:MutationObserver 是规范接班人,批量异步触发,性能好得多。

js 复制代码
const mo = new MutationObserver((records) => {
  records.forEach((r) => {
    if (r.type === "childList") console.log("子节点变了", r.addedNodes);
    else if (r.type === "attributes") console.log("属性变了", r.attributeName);
  });
});
mo.observe(container, { childList: true, attributes: true, subtree: true });

兼容:Chrome 26+ / Firefox 14+ / Safari 7+(2013+)------ 老古董 API 了,放心用。

坑在哪 :回调拿到的 record 不一定包含完整新节点内容,读最新值要重新 query


8. Page Visibility API vs blur / focus

老路子:用 window 的 blur/focus 判断用户切走。

js 复制代码
// ❌ 老路子:blur/focus 在 tab 切换时不靠谱
window.addEventListener("blur", () => pause());
window.addEventListener("focus", () => resume());

现代:visibilitychange 直接告诉你"页面看不看得见"。

js 复制代码
document.addEventListener("visibilitychange", () => {
  if (document.hidden) pauseVideo();
  else resumeVideo();
});

兼容:Chrome 33+ / Firefox 18+ / Safari 7+(2013+)。

坑在哪 :移动端切后台一定触发;桌面端切 tab 触发。但是锁屏/最小化不一定 ------结合 blur 兜底更稳。


三、剪贴板 / 分享(3 个)

9. Clipboard API vs document.execCommand('copy')

老路子:execCommand 一把梭,但已废弃,Safari 还经常弹"是否允许"。

js 复制代码
// ❌ 老路子:已废弃
const input = document.createElement("input");
input.value = "要复制的文字";
document.body.appendChild(input);
input.select();
document.execCommand("copy");

现代:异步 + Promise,干净利落

js 复制代码
await navigator.clipboard.writeText("要复制的文字");
const text = await navigator.clipboard.readText();

兼容:Chrome 66+ / Firefox 63+ / Safari 13.1+(2019+)。

坑在哪必须在 HTTPS 或 localhost(http 页面直接没权限)。用户首次粘贴还要点"允许"。


老路子:弹个自己的 modal,列微信/QQ/微博图标,调用对应 SDK。

js 复制代码
// ❌ 老路子:自己写分享菜单 + 调各家 SDK
showShareModal({ title, url, desc });
// 然后:wx.share({...}), qq.share({...}), wb.share({...})...

现代:调起系统原生分享面板------Android 直接弹分享菜单,iOS 弹分享 sheet。

js 复制代码
navigator.share({
  title: "JS 代码技巧 vol.7",
  text: "浏览器 API 实战,自带 API 能干的事别自己封装",
  url: "https://example.com/post/7",
});

兼容 :Chrome 89+(Android)/ Safari 12+(iOS),桌面浏览器普遍不支持

坑在哪桌面 Chrome / Firefox 不支持------fallback 到自己写的分享菜单。HTTPS 必装。


11. Fullscreen API vs 模拟全屏

老路子:把 position fixed 拉满 100vw/100vh,"看起来"全屏------但地址栏还在。

js 复制代码
// ❌ 老路子:假全屏
video.style.position = "fixed";
video.style.top = 0;
video.style.left = 0;
video.style.width = "100vw";
video.style.height = "100vh";

现代:requestFullscreen 真·全屏(地址栏、任务栏都消失)。

js 复制代码
video.requestFullscreen();
// 退出
document.exitFullscreen();

兼容:Chrome 71+ / Firefox 64+ / Safari 13.1+(2019+)。

坑在哪 :必须用户手势触发 (click 回调里直接调,setTimeout 里调会被拒)。element.requestFullscreen 不是 Element.fullscreen()------拼错不报错。


四、存储(3 个)

老路子:存点小数据用 cookie,结果每个请求都带着------4KB 限制,恶心。

js 复制代码
// ❌ 老路子:cookie 同步存 + 每次请求自动带
document.cookie = "token=abc; path=/";
// 后端每次都拿到,前端还要 decode

现代:localStorage 存客户端数据,不参与 HTTP 请求

js 复制代码
localStorage.setItem("token", "abc");
localStorage.getItem("token");
sessionStorage.setItem("tmp", "value"); // 关 tab 消失

兼容 :全平台支持,IE8+ 都有。唯一限制 ~5MB

坑在哪同步 API ------大 JSON.parse 会阻塞主线程。隐私模式可能被禁用XSS 风险------别存敏感数据。


13. IndexedDB vs localStorage 大数据

老路子:硬塞 localStorage,结果 5MB 限制爆了。

js 复制代码
// ❌ 老路子:localStorage 装 50MB 缓存
localStorage.setItem("cache", JSON.stringify(hugeData));
// 浏览器:QuotaExceededError

现代:IndexedDB 是正经的客户端数据库------GB 级别,异步,索引查询。

js 复制代码
// 简化示意:实际用 idb / dexie 库
const db = await openDB("myapp", 1, {
  upgrade(db) {
    db.createObjectStore("users", { keyPath: "id" });
  },
});
await db.put("users", { id: 1, name: "小不" });

兼容:全平台,IE10+(有限制)。

坑在哪API 极其难用 ------callback 地狱老古董。生产用 Dexie.js 或 idb 库封装事务 model 跟 SQL 不一样


老路子:用户存储爆了才报"磁盘满"------排查半天。

js 复制代码
// ❌ 老路子:瞎写
for (let i = 0; i < 1000000; i++) {
  localStorage.setItem(`k${i}`, "v".repeat(1000));
}
// 浏览器:QuotaExceededError

现代:浏览器自带配额查询。

js 复制代码
const { quota, usage } = await navigator.storage.estimate();
console.log(`已用 ${usage} / ${quota}`);
// 用户即将爆存储时:先删旧数据

兼容:Chrome 61+ / Firefox 57+ / Safari 16.4+(2023+)。

坑在哪 :Safari 老版本不支持。返回的 quota 是估算值,实际可用更小。


五、性能监控(3 个)

老路子:在 window.onloadperformance.timing 算首屏------但这个 API 已废弃。

js 复制代码
// ❌ 老路子:performance.timing 已废弃
const t = performance.timing;
console.log(t.loadEventEnd - t.navigationStart);

现代:PerformanceObserver 监听各种性能条目,实时异步

js 复制代码
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.duration);
  }
});
po.observe({ type: "longtask", buffered: true });
// longtask / paint / resource / measure / navigation...

兼容:Chrome 52+ / Firefox 57+ / Safari 11+(2017+)。

坑在哪 :要 buffered: true 才能拿到observe 之前的记录。不同 type 兼容性不同,老浏览器要 feature detect。


16. requestIdleCallback vs setTimeout(0)

老路子:把低优先级任务塞 setTimeout(0),以为浏览器会安排在空闲时。

js 复制代码
// ❌ 老路子:setTimeout(0) 浏览器不保证"空闲"
setTimeout(() => logAnalytics(), 0);

现代:requestIdleCallback 真·空闲回调------浏览器告诉你"现在不忙"。

js 复制代码
requestIdleCallback((deadline) => {
  // deadline.timeRemaining() 是剩余毫秒
  while (deadline.timeRemaining() > 0) {
    doBackgroundWork();
  }
});

兼容 :Chrome 47+ / Firefox 55+ / Safari 不支持

坑在哪Safari 完全不支持 ------需要 polyfill。超时 fallback 用 setTimeout

js 复制代码
const ric = window.requestIdleCallback || ((fn) => setTimeout(fn, 0));

17. requestAnimationFrame vs setTimeout(16ms)

老路子:用 setTimeout(fn, 16) 模拟 60fps。

js 复制代码
// ❌ 老路子:跟显示器刷新率不同步,可能掉帧
function tick() {
  animate();
  setTimeout(tick, 16);
}

现代:requestAnimationFrame显示器刷新率同步------60Hz、120Hz、144Hz 都跟。

js 复制代码
function tick() {
  animate();
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

兼容:全平台,IE10+。

坑在哪tab 切到后台就暂停 (这是 feature,不是 bug)。别用来做心跳包------关键时刻掉链子。


六、UI / DOM(3 个)

老路子:自己 div + position fixed + z-index + ESC 关闭 + 遮罩 + focus trap------50 行起步。

js 复制代码
// ❌ 老路子:自己写 modal
const modal = document.createElement("div");
modal.style.cssText = "position:fixed;inset:0;z-index:9999;...";
// + ESC 监听 + 遮罩点击关闭 + focus trap + a11y...

现代:<dialog> 元素原生支持 modal,focus trap、ESC、遮罩全内置

html 复制代码
<dialog id="m">
  <h2>弹窗标题</h2>
  <p>内容</p>
  <button onclick="m.close()">关闭</button>
</dialog>
js 复制代码
m.showModal(); // 真 modal,带遮罩 + focus trap
m.close();

兼容 :Chrome 37+ / Firefox 98+ / Safari 15.4+(2022+)。老 Safari 要 polyfill (用 <dialog-polyfill>)。

坑在哪 :原生 <dialog> 的样式默认丑 ------::backdrop 改背景。form 集成要 method="dialog"


19. Element.checkVisibility() vs getBoundingClientRect

老路子:判断元素是否真的可见------getBoundingClientRect + offsetParent + display: none 检查 + ... 写一堆。

js 复制代码
// ❌ 老路子
function isVisible(el) {
  const rect = el.getBoundingClientRect();
  const style = getComputedStyle(el);
  return rect.width > 0 && rect.height > 0 && style.display !== "none";
  // 还有 visibility、opacity、clip-path、content-visibility...
}

现代:el.checkVisibility() 一行------浏览器帮你判断所有可见性

js 复制代码
const visible = el.checkVisibility({
  checkOpacity: true, // opacity: 0 也算不可见
  checkVisibilityCSS: true, // visibility: hidden
  contentVisibilityAuto: true, // content-visibility: auto skip
});

兼容:Chrome 105+ / Firefox 106+ / Safari 17.4+(2023+)------ 比较新。

坑在哪新 API,老项目别用。需要兼容的话,老路子判断要写完整。


20. Element.animate() vs setInterval 动画

老路子:setInterval 改 style.left,30 行起步,还得自己管时间轴。

js 复制代码
// ❌ 老路子
let pos = 0;
const timer = setInterval(() => {
  pos += 5;
  box.style.transform = `translateX(${pos}px)`;
  if (pos > 300) clearInterval(timer);
}, 16);

现代:Element.animate() 走浏览器 compositor,GPU 加速,性能拉满

js 复制代码
box.animate(
  [{ transform: "translateX(0)" }, { transform: "translateX(300px)" }],
  { duration: 1000, easing: "ease-out", fill: "forwards" },
);

兼容:Chrome 36+ / Firefox 48+ / Safari 13.1+(2019+)。

坑在哪 :返回 Animation 对象,要暂停/取消拿引用。fill: 'forwards' 不加动画结束就回弹------踩过这个坑的应该不止我一个。


📦 收个尾

这 20 个技巧浓缩一下:

  • 最高频踩坑:Clipboard 忘了 HTTPS、IntersectionObserver iframe 内失效、setTimeout 模拟 60fps 掉帧
  • 最值得收藏 :fetch + AbortController、URLSearchParams、<dialog> 元素
  • 核心思路:能不用自己封装的,就别自己封装------浏览器自己实现的,通常比你写的又快又稳

学到了就是赚到了,犹豫徘徊等于白来~


写到最后

想要啥技巧?评论区甩个题目过来~

  • 你刚踩的坑
  • 项目里反复写的代码
  • 想搞清楚但一直懒得查的 API

小不看到...不一定回 😂 毕竟代码里翻车太多,腾不出手~

Ps:三连随缘,催更的会被打 😂

相关推荐
CodexDave8 小时前
数据库连接池耗尽:排查顺序与三层兜底
服务器·前端·数据库·git·云原生·容器·kubernetes
Hilaku8 小时前
为什么 Cloudflare 能做到毫秒级冷启动,而其它云厂商却不行?
前端·javascript·程序员
2301_794461578 小时前
JavaScript 基础知识点详解
前端·javascript·html
HjhIron8 小时前
在浏览器中跑DeepSeek-R1:用React+WebGPU实现端侧AI推理
前端·ai编程
早期的虫儿有鸟吃8 小时前
vue2--Vuex 模块化
开发语言·前端·javascript
程序员清风8 小时前
推荐几个我常听的AI播客!
java·后端·面试
C++、Java和Python的菜鸟8 小时前
第5章 后端Web基础 (MySQL基础)
前端·mysql·adb
门前大桥下.8 小时前
HTML-01我的第一个网页
前端·html
我是大卫9 小时前
【图】React源码解析-从数据结构、依赖追踪机制、值传播与更新触发、以及性能陷阱与优化,深挖useContext的底层原理
前端·react.js·源码