本文将带你探索 10 个能够显著提升前端性能的高级 API
IntersectionObserver
解决的问题: 传统监听滚动事件实现懒加载会造成大量的性能开销,因为需要频繁触发并调用 getBoundingClientRect(),导致重排。
API 简介: IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。
当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。
性能提升点: 避免滚动时的强制同步布局和频繁的 JavaScript 执行,极大提升滚动性能。
// 图片懒加载实战
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target; // 将 data-src 的值赋给 src
img.src = img.dataset.src;
img.classList.remove("lazy"); // 图片加载后停止观察
lazyImageObserver.unobserve(img);
}
});
});
// 观察所有带有 lazy 类的图片
document.querySelectorAll("img.lazy").forEach((img) => {
lazyImageObserver.observe(img);
});
VirtualKeyboard API
解决的问题: 在移动端,输入框聚焦时弹出的软键盘会挤压视口高度,导致页面布局错乱,常见于 position: fixed 的底部元素被顶起。
API 简介: 虚拟键盘 API 的 VirtualKeyboard 接口用于具有屏幕虚拟键盘的设备(如平板电脑、手机或其他没有物理键盘的设备)。
VirtualKeyboard 接口使你可以选择不使用浏览器自动处理屏幕虚拟键盘的方式——通过减少视口的高度来为虚拟键盘腾出空间。你可以阻止浏览器改变视口大小、检测虚拟键盘的位置和大小,并通过编程方式显示或隐藏虚拟键盘。
性能与体验提升点: 避免使用不稳定的 window.resize 事件或定时器去猜测键盘状态,实现平滑、自适应的布局调整。
if ("virtualKeyboard" in navigator) {
const virtualKeyboard = navigator.virtualKeyboard; // 监听键盘几何变化
virtualKeyboard.addEventListener("geometrychange", (event) => {
const { x, y, width, height } = event.target.geometry; // 调整底部固定元素的位置
const bottomElement = document.getElementById("bottom-bar");
if (height > 0) {
// 键盘弹出,将元素上推键盘的高度
bottomElement.style.transform = `translateY(-${height}px)`;
} else {
// 键盘收起,恢复原位
bottomElement.style.transform = "translateY(0)";
}
}); // 在输入框聚焦时显示虚拟键盘(如果需要)
document.getElementById("my-input").addEventListener("focus", () => {
virtualKeyboard.show();
});
}
Content Visiblity: auto 与 contain-intrinsic-size
解决的问题: 长列表或复杂文档的初始渲染和滚动会非常卡顿,因为浏览器需要为所有元素进行布局和绘制,即使它们不在视口内。
API 简介: CSS 属性 content-visibility 控制元素是否渲染其内容,以及施加一组强局限,由此允许用户代理有机会在不需要时省略大片的布局和渲染工作。此属性使用户代理得以在不需要时跳过元素的渲染工作(包括布局和绘制)——由此使页面的初始加载明显变快。
CSS 简写属性 contain-intrinsic-size 定义了元素受尺寸局限时浏览器用于布局的元素尺寸。以避免滚动条抖动。
性能提升点: 极大减少初始加载的渲染工作量,提升首屏渲染(FCP)和可交互时间(TTI),实现如原生应用般流畅的滚动。
<style>
.long-list-item {
content-visibility: auto;
/* 提供一个近似的高度值,避免滚动条跳动 */
contain-intrinsic-size: 200px;
}
</style>
<div class="long-list">
<div class="long-list-item">项目 1</div>
<div class="long-list-item">项目 2</div>
<!-- ... 成百上千个项目 -->
</div>
PerformanceObserver
解决的问题: 传统的 performance.getEntries() 只能获取历史性能数据,无法实时监控应用在整个生命周期中产生的性能指标。
API 简介: PerformanceObserver 用于监测性能度量事件,在浏览器的性能时间轴记录新的 performance entry 的时候将会被通知。它是监控诸如 LCP(最大内容绘制)、FID(首次输入延迟)、CLS(累积布局偏移)等 Core Web Vitals 的推荐方式。
性能提升点: 提供精准、实时的性能数据,帮助你在生产环境中定位和解决性能问题。
// 监控 Largest Contentful Paint
const lcpObserver = new PerformanceObserver((entryList) => {
const entries = entryList.getEntries(); // 最后一个条目是最大的一个
const lastEntry = entries[entries.length - 1];
console.log("LCP candidate:", lastEntry.startTime, lastEntry); // 在这里将 LCP 值发送到你的监控服务
});
lcpObserver.observe({ entryTypes: ["largest-contentful-paint"] });
// 监控 Layout Shifts
let clsValue = 0;
const clsObserver = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
console.log("Current CLS value:", clsValue);
}
}
});
clsObserver.observe({ entryTypes: ["layout-shift"] });
requestIdleCallback
解决的问题: 一些非紧急任务(如日志上报、预加载非关键资源)如果与用户的关键操作(如动画、输入)争抢主线程,会导致卡顿。
API 简介:window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。它还会提供一个 IdleDeadline 参数,告诉你还剩多少空闲时间。
性能提升点: 将任务拆分并在空闲时间执行,确保主线程优先响应用户交互,提升应用的流畅度。
function scheduleNonCriticalWork() {
requestIdleCallback((deadline) => {
// 如果当前帧的空闲时间还足够,或者任务不是特别紧急
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
doSomeNonCriticalTask(tasks.shift());
} // 如果任务还没做完,继续安排到下一个空闲期
if (tasks.length > 0) {
scheduleNonCriticalWork();
}
});
}
MutationObserver
解决的问题: 使用 setInterval 或监听不特定事件来检测 DOM 变化,效率低下且不准确。
API 简介: MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
性能提升点: 批量、异步地处理 DOM 变化,避免在每次微小变化时都触发高开销的回调函数。
// 动态加载第三方插件时,确保其所需的 DOM 已存在
const pluginContainer = document.getElementById("plugin-container");
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === "childList") {
// 检查是否有新的节点添加,并且是第三方插件需要的结构
if (document.querySelector(".third-party-widget")) {
loadThirdPartyPlugin(); // 加载后即可断开观察
observer.disconnect();
}
}
}
});
observer.observe(pluginContainer, { childList: true, subtree: true });
Broadcast Channel API
解决的问题: 在多个同源标签页间共享状态(如登录状态、主题设置)通常使用 LocalStorage 事件,但该事件仅在非当前标签页触发,且同步 API 有性能风险。
API 简介: BroadcastChannel 接口表示给定源的任何浏览上下文都可以订阅的命名频道。它允许同源的不同浏览器窗口、标签页、frame 或者 iframe 下的不同文档之间相互通信。消息通过 message 事件进行广播,该事件在侦听该频道的所有 BroadcastChannel 对象上触发,发送消息的对象除外。
性能与体验提升点: 提供了一种比 LocalStorage 更直接、更高效、无性能风险的通信方式,避免了不必要的存储操作。
// 标签页 A - 发送消息
const broadcast = new BroadcastChannel("app-channel");
broadcast.postMessage({ type: "USER_LOGGED_IN", userId: 123 });
// 标签页 B - 接收消息
const broadcast = new BroadcastChannel("app-channel");
broadcast.onmessage = (event) => {
if (event.data.type === "USER_LOGGED_IN") {
// 更新本页面的用户状态
updateUIForLoggedInUser(event.data.userId);
}
};
Web Worker
解决的问题: JavaScript 是单线程的。复杂的计算(如图像处理、数据排序、加密)会阻塞主线程,导致页面无响应。
API 简介:Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,它们可以使用 XMLHttpRequest(尽管 responseXML 和 channel 属性总是为空)或 fetch(没有这些限制)执行 I/O。一旦创建,一个 worker 可以将消息发送到创建它的 JavaScript 代码,通过将消息发布到该代码指定的事件处理器(反之亦然)。
性能提升点: 解放主线程,确保 UI 始终流畅,即使在进行繁重计算时。
主线程代码:
const myWorker = new Worker("worker.js");
// 向 Worker 发送数据
myWorker.postMessage(largeDataArray);
// 接收来自 Worker 的结果
myWorker.onmessage = function (e) {
const processedData = e.data;
console.log("处理完成的数据:", processedData);
};
worker.js:
// 在 Worker 内部监听消息
onmessage = function (e) {
const data = e.data; // 执行一些昂贵的计算,不会阻塞主线程
const result = expensiveCalculation(data); // 将结果发送回主线程
postMessage(result);
};
function expensiveCalculation(data) {
// ... 复杂的处理逻辑 ...
return processedData;
}
ResizeObserver
解决的问题: 使用 window.resize 监听整个窗口变化,然后通过 getBoundingClientRect() 获取元素尺寸,效率低下且无法直接响应特定元素的大小变化。
API 简介: ResizeObserver 接口监视 Element 内容盒或边框盒或者 SVGElement 边界尺寸的变化。
性能提升点: 提供了一种高性能、针对性的方式来响应布局变化,避免了在全局 resize 事件中执行大量重复的布局查询。
// 响应式图表重绘
const chartElement = document.getElementById("my-chart");
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect; // 当图表容器尺寸变化时,重新绘制图表
myChart.resize(width, height);
}
});
resizeObserver.observe(chartElement);
fetchpriority
解决的问题: 浏览器虽然有自己的资源加载优先级算法,但并非总是完美。对于关键资源(如首屏英雄图像、关键 CSS),我们希望给予浏览器明确的提示。
API 简介: <link> 元素的 fetchpriority 属性为浏览器提供了一个提示,指示它应如何相对于同类型的其他资源来优先获取特定资源。
性能提升点: 通过优先加载关键资源,延迟加载非关键资源,优化 LCP 和 FCP 指标。
<!-- 告诉浏览器这是一个高优先级的 LCP 候选元素 -->
<img src="hero-image.jpg" fetchpriority="high" alt="Hero Image">
<!-- 一个在页面底部的不重要图片,可以低优先级加载 -->
<img src="decoration.png" fetchpriority="low" alt="Decoration">