Skip to main content

BOM

BOM (浏览器对象模型) 提供了 JS 与浏览器窗口交互的接口。面试中最核心的考点集中在本地存储方案的对比前端路由的底层原理 (History API)Web Worker 多线程通信,以及现代浏览器提供的一系列高性能 Observer API

核心接口概览

  • window:浏览器环境的全局对象(this 默认指向),包含当前窗口信息。
  • navigator:包含浏览器版本、系统信息(常用于判断设备类型和 UA 嗅探)。
  • location:URL 相关信息和操作。
  • history:浏览历史记录操作。

前端路由的底层原理 (Hash vs History)

在 Vue/React 的单页应用 (SPA) 中,路由主要有 Hash 和 History 两种模式,它们都依赖 BOM 提供的 API。

模式底层 API特点与缺点
Hash 模式window.onhashchangeURL 中带有 #。修改 # 后面的内容不会引起页面刷新。兼容性极好,不需要后端配置配合。缺点是 URL 不美观。
History 模式history.pushStatehistory.replaceStateURL 干净美观。可以通过 API 改变 URL 而不刷新页面。致命缺点:刷新页面时如果后端没有配置对应的路由重定向,会直接报 404,必须要求 Nginx/后端 配置所有请求都回退到 index.html

浏览器本地存储对比

这是前端面试的必考八股文:CookieLocalStorageSessionStorageIndexedDB 的区别?

方案存储大小生命周期携带性适用场景
Cookie4KB可设置过期时间,否则关闭浏览器失效每次 HTTP 请求都会自动携带在 Header 中,严重影响带宽存储用户登录凭证 (Token/SessionId)
LocalStorage5MB左右永久存储,除非手动清除不参与服务器通信存储用户偏好设置、长期缓存数据
SessionStorage5MB左右会话级别,关闭当前标签页/窗口即销毁不参与服务器通信表单草稿、单次会话临时数据
IndexedDB不限量永久存储不参与服务器通信存储大量结构化数据、离线应用数据

注意坑点SessionStorage 的生命周期是“当前标签页”。如果你在新标签页中(通过新窗口打开)进入同一个页面,它是拿不到原来标签页的 SessionStorage 的!


5. 高频面试考点 API 补充

除了上述基础的 BOM 操作,现代浏览器还提供了一些非常重要的高阶 API,它们在性能优化和复杂业务场景中是高频考点。

① IntersectionObserver (相交观察者)

  • 用途:判断一个 DOM 元素是否进入了可视区域(Viewport)。
  • 实战场景:图片懒加载、无限滚动列表(下拉加载更多)、广告曝光埋点。
  • 面试杀手锏:以前做懒加载都是监听 scroll 事件然后调 getBoundingClientRect() 计算,这会导致主线程频繁执行强制同步布局 (Layout Thrashing),引发严重卡顿。IntersectionObserver 是由浏览器底层异步观察的,完全不阻塞主线程,性能极高。
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log("元素进入了可视区域!可以加载图片或发埋点了");
// 图片懒加载:把 data-src 赋值给 src
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target); // 加载完后停止观察
}
});
});
observer.observe(document.querySelector(".lazy-img"));

② MutationObserver (DOM 变动观察者)

  • 用途:监听 DOM 树的结构或属性变化(如节点增删、属性修改)。
  • 实战场景
    • 富文本编辑器中监听内容变化。
    • 防作弊水印:如果用户用开发者工具删除了网页上的水印 DOM,MutationObserver 可以立刻监听到并重新把水印加回去。
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === "childList" && mutation.removedNodes.length > 0) {
console.log("警告:水印节点被删除了!正在重新生成...");
// 重新生成水印逻辑...
}
}
});
observer.observe(document.getElementById("watermark-container"), {
childList: true, // 观察子节点的变化
subtree: true, // 观察所有后代节点
});

③ MessageChannel (宏任务调度)

  • 用途:创建一个独立的双向通信管道。
  • 实战场景
    • Web Worker 通信:将其中一个 port 传给 Worker 实现数据交换。
    • Vue/React 底层调度高频面试题! React 底层的 Scheduler(调度器)在模拟时间切片时,为什么不用 setTimeout(fn, 0)?因为 setTimeout 在嵌套层级较深时会有最低 4ms 的延迟。React 为了榨干每一毫秒,使用了 MessageChannel 来触发一个极速的宏任务
const channel = new MessageChannel();
// Port1 监听消息
channel.port1.onmessage = (e) => {
console.log("收到极速宏任务触发:", e.data);
};
// Port2 发送消息(会立刻被放入宏任务队列,且没有 4ms 的历史包袱延迟)
channel.port2.postMessage("React 调度器发来贺电");

④ requestAnimationFrame (rAF)

  • 用途:告诉浏览器你希望执行一个动画,并要求浏览器在下次重绘之前调用指定的函数。
  • 特点:执行频率与显示器的刷新率严格同步(通常是 60Hz,即 16.6ms 一次)。
  • 实战场景
    • JS 动画(完全抛弃 setInterval)。
    • 面试高频:当页面隐藏或最小化时,rAF 会自动暂停执行,以节省 CPU 开销,而 setInterval 会在后台继续无脑空转。
let start = null;
const element = document.getElementById("box");

function step(timestamp) {
if (!start) start = timestamp;
const progress = timestamp - start;
element.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`;

if (progress < 2000) {
// 动画执行 2 秒
requestAnimationFrame(step);
}
}
requestAnimationFrame(step); // 启动动画

⑤ navigator.sendBeacon

  • 用途:在浏览器关闭或卸载页面时(unload / beforeunload),异步且可靠地向服务器发送少量数据。
  • 实战场景前端监控埋点。以前在页面关闭时发 Ajax 请求很容易被浏览器强行取消,用 sendBeacon 则是由浏览器底层排队发送,保证数据绝对不丢。
window.addEventListener("unload", () => {
const data = JSON.stringify({ userId: 123, stayTime: 5000 });
// 即使页面立刻关闭,浏览器也会在后台把这个请求发完
navigator.sendBeacon("/api/log", data);
});

Web Worker

面试防坑警告:经常有面试官问“Web Worker 是不是意味着 JS 变成了多线程语言?” 标准回答绝对不是! JavaScript 语言本身(及其引擎如 V8)依然是纯粹的单线程。Web Worker 是浏览器宿主环境提供的一种并发机制。浏览器在底层开辟了一个完全独立的系统线程来运行 Worker 脚本,这个线程拥有自己独立的全局上下文(DedicatedWorkerGlobalScope),它不能访问 DOM,也不能访问主线程的 window 对象。两者之间只能通过 postMessage 进行数据的序列化拷贝通信。

Web Worker 主要是为了解决CPU 密集型计算(如前端处理大型 Excel、图片滤镜矩阵运算、复杂的加解密)导致主线程阻塞(页面卡死)的问题。

核心应用场景

  • 复杂数据处理(如前端解析几十兆的 Excel/CSV 文件)。
  • 图像/视频/音频的底层处理(结合 WebAssembly)。
  • 复杂的加密、解密算法。

局限性与坑点

  • DOM 限制:Worker 线程所在的全局对象是 self,它没有 windowdocument,因此绝对不能在 Worker 里操作 DOM!可以使用 XMLHttpRequestfetchsetTimeout 等。
  • 同源限制:分配给 Worker 的脚本文件,必须与主线程同源。
  • 通信成本:主线程与 Worker 之间通过 postMessage 通信,传数据时默认是深拷贝(结构化克隆)。如果你传一个 100MB 的对象,光拷贝就会卡死主线程!
    • 极致优化方案:使用 Transferable Objects(如 ArrayBuffer)。它是“零拷贝”的权限转移,把内存的所有权直接交给 Worker,主线程立刻失去对它的访问权,速度极快,是面试加分项!