JavaScript BOM(浏览器对象模型)

BOM(Browser Object Model)是浏览器暴露给 JS 的一组对象,让我们能控制浏览器本身:窗口、地址栏、历史记录、屏幕、剪贴板。如果说 DOM 操作的是"网页内容",BOM 操作的就是"浏览器外壳"。这一章我们学习 BOM 的核心对象。

1. window:顶层对象

window 是浏览器 JS 的全局对象——所有全局变量、内置函数都是它的属性。前面用过的 alertconsoledocument 都是 window 的属性,只是平时省略不写。

// window 是浏览器的顶层对象,所有全局变量都是它的属性
console.log(window.innerWidth);     // 视口宽度(像素)
console.log(window.innerHeight);    // 视口高度
console.log(window.devicePixelRatio); // 设备像素比(高分屏 = 2 或 3)

// 窗口尺寸
window.resizeTo(800, 600);          // 调整窗口大小(会被弹窗拦截)

// 弹窗(不推荐用于产品,只用调试)
alert("提示");
confirm("确定要删除吗?");          // 返回 true / false
prompt("请输入姓名:");             // 返回输入值或 null

// 滚动
window.scrollTo(0, 500);            // 滚到指定位置
window.scrollTo({ top: 500, behavior: "smooth" });  // 平滑滚动
window.scrollBy(0, 100);            // 相对滚动 100px

// 全局变量 = window 属性
var x = 1;
console.log(window.x);    // 1(var 会成为 window 属性)
let y = 2;
console.log(window.y);    // undefined(let 不会)

实际开发中 window 通常省略(如直接写 alert),只在需要消歧义访问 window 自身属性时显式写出。

2. location:URL 与跳转

location 是页面 URL 的"遥控器",既能读取 URL 各部分,也能跳转页面:

// location:当前页面 URL 信息
console.log(location.href);        // 完整 URL
console.log(location.protocol);    // "https:"
console.log(location.host);        // "example.com:8080"
console.log(location.hostname);    // "example.com"
console.log(location.port);        // "8080"
console.log(location.pathname);    // "/page/article"
console.log(location.search);      // "?id=1&name=x"
console.log(location.hash);        // "#section-1"(锚点)

// 跳转页面
location.href = "https://example.com";   // 跳转(留历史记录)
location.replace("https://example.com"); // 跳转(不留历史,不能后退)
location.reload();                       // 刷新页面

// 修改 URL 参数(添加参数不刷新)
const url = new URL(location.href);
url.searchParams.set("page", "2");
url.searchParams.get("page");      // "2"
history.pushState({}, "", url);    // 改 URL 不刷新(SPA 路由原理)

URL 对象(new URL())是处理 URL 参数的现代方式,比手动 split 字符串可靠得多。配合 history.pushState 实现 SPA 路由的核心原理。

3. history:前进后退

// history:浏览器历史记录
history.back();      // 后退
history.forward();   // 前进
history.go(-2);      // 后退 2 步
history.go(1);       // 前进 1 步

// SPA 路由:不刷新页面,改 URL
history.pushState({ page: 1 }, "", "/list");
history.pushState({ page: 2 }, "", "/list/2");

// 监听前进/后退(浏览器按钮或 history.go)
window.addEventListener("popstate", (e) => {
  console.log("URL 变了:", location.pathname);
  console.log("状态:", e.state);
});

// replaceState:替换当前记录(不留新历史)
history.replaceState({ page: 1 }, "", "/home");

History API 是单页应用(SPA)的基石。React Router、Vue Router 等"前端路由"库,底层就是 pushState + popstate 监听——不刷新页面改 URL,响应用户的前进后退按钮。

4. 定时器

setTimeoutsetInterval 是 JS 里安排"未来执行代码"的方式:

// setTimeout:延迟执行一次
const timer1 = setTimeout(() => {
  console.log("3 秒后执行");
}, 3000);

// 取消
clearTimeout(timer1);

// setInterval:重复执行
const timer2 = setInterval(() => {
  console.log("每 1 秒执行");
}, 1000);

// 取消
clearInterval(timer2);

// 实战:倒计时
let count = 10;
const timer = setInterval(() => {
  console.log(count);
  count--;
  if (count < 0) {
    clearInterval(timer);   // 别忘了清,否则一直跑
    console.log("结束!");
  }
}, 1000);

// ⚠️ 计时器不精确!最小延迟 ~4ms,后台标签页会被节流到 1000ms
// 需要精确计时用 performance.now() + requestAnimationFrame

记忆口诀:

定时器的坑:JS 是单线程的,定时器只是"在主线程空闲时尽快执行",不是精确时间。后台标签页会被浏览器节流到 1s,手机省电模式更慢。需要精确动画用 requestAnimationFrame,需要精确计时用 performance.now()

务必记得 clearInterval:组件销毁时不清定时器,会造成内存泄漏和 bug。React 的 useEffect 必须返回清理函数就是这个原因。

5. localStorage 与 sessionStorage

这是浏览器提供的本地存储,可以让网页在用户关闭后再打开时,仍然记得数据:

// localStorage:永久存储(关闭浏览器也在)
// sessionStorage:会话存储(关闭标签页就清空)
// 两者 API 完全一样,只是生命周期不同

// 存(必须是字符串,对象要先 JSON.stringify)
localStorage.setItem("username", "小明");
localStorage.setItem("user", JSON.stringify({ name: "小明", age: 20 }));

// 取
const name = localStorage.getItem("username");   // "小明"
const user = JSON.parse(localStorage.getItem("user"));

// 删
localStorage.removeItem("username");

// 清空所有
localStorage.clear();

// 遍历所有 key
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key, localStorage.getItem(key));
}

// ⚠️ 容量限制(约 5-10MB),同步 API 会阻塞,大用 IndexedDB

三个注意点:

  1. 只能存字符串,对象要先 JSON.stringify
  2. 容量约 5-10MB,超过会抛异常,需要 try/catch。
  3. 不要存敏感信息(密码、token 明文)——XSS 攻击会偷走。需要安全存储用 HttpOnly Cookie。

6. navigator:浏览器与设备

// navigator:浏览器信息
console.log(navigator.userAgent);     // 浏览器标识字符串
console.log(navigator.language);      // "zh-CN"
console.log(navigator.platform);      // "MacIntel" / "Win32"

// 检测是否在线
console.log(navigator.onLine);        // true / false
window.addEventListener("online", () => console.log("联网了"));
window.addEventListener("offline", () => console.log("断网了"));

// 剪贴板
navigator.clipboard.writeText("复制的内容").then(() => {
  console.log("复制成功");
});
navigator.clipboard.readText().then(text => {
  console.log("粘贴板内容:", text);
});

// 地理定位(需要用户授权)
navigator.geolocation.getCurrentPosition(pos => {
  console.log(pos.coords.latitude, pos.coords.longitude);
});

// 屏幕
console.log(screen.width);            // 屏幕分辨率宽
console.log(screen.height);
console.log(screen.availWidth);       // 可用宽度(去除任务栏)

navigator.userAgent 可以用来判断浏览器类型(Chrome / Safari / 微信内置浏览器),但现代开发推荐用特性检测(if ('geolocation' in navigator))而不是 UA 嗅探——UA 字符串很容易伪造,且版本繁多。

7. screen 屏幕

console.log(screen.width, screen.height);     // 物理分辨率
console.log(screen.availWidth, screen.availHeight); // 可用区域(扣除任务栏)
console.log(screen.colorDepth);              // 颜色深度,通常是 24

// 媒体查询(JS 版,响应式必备)
const mq = window.matchMedia("(max-width: 768px)");
console.log(mq.matches);   // 视口是否 <= 768px

mq.addEventListener("change", (e) => {
  console.log(e.matches ? "切换到手机" : "切换到桌面");
});

// 全屏 API
document.documentElement.requestFullscreen();   // 进入全屏
document.exitFullscreen();                     // 退出全屏

8. 实战:页面加载完成、滚动监听

// DOM 加载完成(不等图片等资源,推荐)
document.addEventListener("DOMContentLoaded", () => {
  console.log("DOM 准备好了,可以操作元素");
});

// 整个页面(含图片)加载完成
window.addEventListener("load", () => {
  console.log("所有资源加载完成");
});

// 页面卸载(刷新/关闭)
window.addEventListener("beforeunload", (e) => {
  e.preventDefault();
  e.returnValue = "";   // 浏览器会弹"确定离开吗?"
});

// 滚动监听(实战:吸顶导航 / 懒加载)
let ticking = false;
window.addEventListener("scroll", () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      console.log(window.scrollY);   // 当前滚动位置
      ticking = false;
    });
    ticking = true;
  }
});

9. 实战:用户偏好记忆

// 进入网站时,读取上次的主题偏好
const saved = localStorage.getItem("theme") || "light";
document.body.classList.toggle("dark", saved === "dark");

// 用户切换主题时,保存
document.querySelector("#themeBtn").addEventListener("click", () => {
  const isDark = document.body.classList.toggle("dark");
  localStorage.setItem("theme", isDark ? "dark" : "light");
});

// 实战:购物车本地暂存
function saveCart(cart) {
  localStorage.setItem("cart", JSON.stringify(cart));
}
function loadCart() {
  try {
    return JSON.parse(localStorage.getItem("cart")) || [];
  } catch {
    return [];   // 解析失败时给默认值
  }
}

小结

下一篇我们看 JS 最有特色的异步编程——Promise 与 async/await,这是处理网络请求、定时器、文件 IO 的关键。

← 上一篇 JavaScript 事件

下一篇 JavaScript 异步与 Promise

✈️💬