BOM 详解
DOM 操作页面内容,BOM(Browser Object Model,浏览器对象模型)则负责操作浏览器本身:地址栏、历史记录、窗口尺寸、浏览器能力。核心对象是 window,几乎所有 BOM 能力都挂在它下面。
一、BOM 概念与组成
BOM 是浏览器提供的、独立于文档内容的对象层级,核心组成:
window(全局对象)
├── location 地址栏与 URL 信息
├── history 浏览历史记录
├── navigator 浏览器与设备信息
├── screen 屏幕信息
├── document DOM 入口(window 的文档部分)
├── 对话框 alert / confirm / prompt
└── 定时器 setTimeout / setInterval| 对象 | 职责 | 典型成员 |
|---|---|---|
window | 全局对象、窗口本身 | innerWidth、open() |
location | 当前 URL 与跳转 | href、reload() |
history | 历史栈与路由 | pushState() |
navigator | 浏览器/设备能力 | userAgent、geolocation |
screen | 屏幕信息 | width、availHeight |
由于 window 是全局对象,window.location 可以直接写 location,window.alert() 可以直接写 alert()。
二、window 对象
2.1 全局对象特性
var a = 10; // var 声明的全局变量会挂到 window 上
console.log(window.a); // 10
let b = 20; // let/const 不会
console.log(window.b); // undefined
window.custom = "自定义全局属性"; // 显式挂载// 视口尺寸(随窗口大小变化)
window.innerWidth; // 视口宽度(含滚动条)
window.innerHeight; // 视口高度
window.outerWidth; // 整个窗口宽度
window.outerHeight; // 整个窗口高度
// 屏幕偏移与滚动
window.screenX; // 窗口左边缘距屏幕左边距
window.screenY;
window.scrollX; // 当前横向滚动距离(等价 window.pageXOffset)
window.scrollY;
// 滚动控制
window.scrollTo(0, 100); // 绝对定位滚动
window.scrollBy(0, 50); // 相对滚动
window.scrollTo({ top: 0, behavior: "smooth" }); // 平滑回顶2.2 open / close 与尺寸控制
// 打开新窗口,返回新窗口的引用
const win = window.open("https://example.com", "_blank", "width=400,height=300");
win.close(); // 关闭(只能关闭自己打开的窗口)
// 调整窗口(多数被浏览器禁用或限制)
window.resizeTo(800, 600);
window.moveTo(100, 100);| 方法 | 作用 | 注意 |
|---|---|---|
window.open(url, target, features) | 打开新窗口/标签页 | 非用户手势触发会被拦截 |
window.close() | 关闭窗口 | 只能关自己打开的窗口 |
resizeTo(w, h) / moveTo(x, y) | 调整大小/位置 | 现代浏览器普遍禁用 |
window.focus() | 窗口获得焦点 | 配合 open 使用 |
三、location 对象
location 保存当前 URL 的全部信息,且可以直接赋值实现跳转:
// 完整 URL:https://example.com:8080/path/page?name=a#section
location.href; // 完整地址,可写:location.href = "https://example.com"
location.protocol; // "https:"
location.host; // "example.com:8080"(含端口)
location.hostname; // "example.com"
location.port; // "8080"
location.pathname; // "/path/page"
location.search; // "?name=a"(查询字符串)
location.hash; // "#section"
location.origin; // "https://example.com:8080"| 属性/方法 | 作用 |
|---|---|
href | 读写完整 URL,赋值即跳转 |
search | 查询字符串,常用于读取参数 |
hash | 锚点,常用于单页应用路由 |
reload() | 刷新页面(参数 true 强制从服务器拉取,已废弃) |
assign(url) | 跳转并留下历史记录 |
replace(url) | 跳转但不留历史(不能后退回来) |
// 解析查询参数
function getQueryParam(key) {
return new URLSearchParams(location.search).get(key);
}
console.log(getQueryParam("name")); // "a"
// 跳转方式的区别
location.assign("https://example.com"); // 可后退
location.replace("https://example.com"); // 不可后退,常用于登录后替换掉登录页
// 修改 hash 不刷新页面(会留下历史)
location.hash = "#home";
window.addEventListener("hashchange", () => {
console.log("当前锚点:", location.hash);
});四、history 对象
4.1 前进后退
history.back(); // 后退一步(等价 history.go(-1))
history.forward(); // 前进一步
history.go(-2); // 后退两步
history.length; // 历史栈中条目数量4.2 History API:单页应用路由的基石
pushState / replaceState 可以在不刷新页面的情况下改变 URL,并配合 popstate 事件完成前端路由:
// 压入一条新历史(url 必须同源)
history.pushState({ page: 1 }, "", "/list?page=1");
// 替换当前历史(不留新条目)
history.replaceState({ page: 1 }, "", "/list?page=1");
// 前进/后退时触发
window.addEventListener("popstate", (event) => {
console.log("当前状态:", event.state); // pushState 传入的数据
});| 对比项 | 传统跳转 | pushState | hash |
|---|---|---|---|
| 是否刷新 | 是 | 否 | 否 |
| URL 形态 | 任意路径 | 任意同源路径 | 带 # |
| 历史记录 | 有 | 有 | 有 |
| 前进后退监听 | 页面重载 | popstate | hashchange |
| 适合场景 | 普通跳转 | SPA 路由 | 简单锚点/兼容老场景 |
// 简单 SPA 路由
document.querySelectorAll("a[data-route]").forEach((a) => {
a.addEventListener("click", (event) => {
event.preventDefault();
const path = a.dataset.route;
history.pushState({ path }, "", path);
render(path); // 自己的渲染逻辑
});
});
window.addEventListener("popstate", (event) => {
render(location.pathname); // 浏览器前进后退时同步视图
});注意:pushState 后 hashchange 不触发、页面不重载,需要自行更新视图;服务端要配置好对应路径的降级(如回退到 index.html),否则直接访问深链接会 404。
五、navigator 对象
navigator 提供浏览器与设备能力信息,是"能力检测"(而非 UA 字符串判断)的入口:
navigator.userAgent; // 用户代理字符串,如 "Mozilla/5.0 ..."
navigator.platform; // 平台,如 "Win32"(已废弃,谨慎用)
navigator.language; // 首选语言,如 "zh-CN"
navigator.languages; // 语言数组
navigator.onLine; // 是否在线(布尔)
navigator.cookieEnabled; // cookie 是否可用
// 在线状态变化监听
window.addEventListener("online", () => console.log("网络恢复"));
window.addEventListener("offline", () => console.log("网络断开"));
// 能力检测示例
if ("serviceWorker" in navigator) {
// 支持 Service Worker,再初始化离线能力
}
if ("geolocation" in navigator) {
// 支持地理定位
}5.1 geolocation 地理定位
navigator.geolocation.getCurrentPosition(
(position) => {
console.log("纬度:", position.coords.latitude);
console.log("经度:", position.coords.longitude);
console.log("精度:", position.coords.accuracy);
},
(error) => {
console.error("定位失败:", error.message); // 权限拒绝、超时等
},
{ timeout: 10000, maximumAge: 60000 }
);需要用户授权,且要求 HTTPS 环境;页面关闭后继续追踪位置用 watchPosition。
5.2 clipboard 剪贴板
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
console.log("已复制");
} catch (error) {
console.error("复制失败(需授权或 HTTPS):", error);
}
}| 能力 | API | 前提 |
|---|---|---|
| 复制文本 | navigator.clipboard.writeText(text) | HTTPS + 用户授权 |
| 读取文本 | navigator.clipboard.readText() | 同上 |
| 传统兜底 | document.execCommand("copy") | 老版本浏览器 |
六、screen 对象
screen.width; // 屏幕物理宽度
screen.height; // 屏幕物理高度
screen.availWidth; // 可用宽度(去掉任务栏)
screen.availHeight; // 可用高度
screen.colorDepth; // 色彩位数,通常 24
screen.pixelRatio; // 设备像素比(window.devicePixelRatio 更常用)| 对比项 | screen | window.innerWidth |
|---|---|---|
| 含义 | 整个屏幕 | 浏览器视口 |
| 固定性 | 基本不变 | 随窗口大小变化 |
| 用途 | 判断设备屏幕 | 判断布局空间 |
七、对话框与定时器
7.1 三类对话框
alert("提示信息"); // 纯提示,只有一个确定
const ok = confirm("确定要删除吗?"); // 确定/取消,返回布尔
const name = prompt("请输入姓名", "默认值"); // 输入框,返回字符串或 null| 方法 | 返回 | 用途 | 注意 |
|---|---|---|---|
alert(msg) | 无 | 提示 | 阻塞页面,少用 |
confirm(msg) | 布尔 | 二次确认 | 无法自定义样式 |
prompt(msg, def) | 字符串/null | 简单输入 | 用户点取消返回 null |
对话框会同步阻塞脚本与页面交互,破坏用户体验,正式产品应优先用自定义弹层组件。
7.2 定时器归属
setTimeout / setInterval 也是 window 的方法,因此可以直接调用。关于定时器语义的细节(最小延迟、嵌套钳制、宏任务位置),参见定时器专题。
window.setTimeout(() => console.log("1 秒后"), 1000);| 定时器 API | 归属 | 返回值 |
|---|---|---|
setTimeout | window(全局) | 数字 id |
setInterval | window(全局) | 数字 id |
requestAnimationFrame | window(全局) | 数字 id |
八、window.open 与弹窗拦截
浏览器只允许"用户手势触发的同步调用"打开新窗口,异步或非手势调用会被拦截:
// 会被拦截:异步打开
setTimeout(() => {
window.open("https://example.com"); // 大概率被浏览器拦截
}, 1000);
// 正确做法:同步打开(可先开空窗再设置地址)
const win = window.open("", "_blank");
fetch("/api/data")
.then((res) => res.json())
.then((data) => {
win.location.href = data.url; // 在新窗口里跳转,不触发拦截
});| 触发方式 | 是否拦截 |
|---|---|
用户点击的同步回调中 window.open | 放行 |
| 异步回调(setTimeout / Promise 之后) | 拦截 |
| 脚本加载时直接调用 | 拦截 |
九、综合示例:基于 History API 的分页导航
<button id="prev">上一页</button>
<button id="next">下一页</button>
<span id="page">第 1 页</span>let page = Number(new URLSearchParams(location.search).get("page")) || 1;
function render() {
document.querySelector("#page").textContent = `第 ${page} 页`;
history.replaceState({ page }, "", `?page=${page}`); // 不新增历史条目
}
document.querySelector("#next").addEventListener("click", () => {
page++;
render();
});
document.querySelector("#prev").addEventListener("click", () => {
page = Math.max(1, page - 1);
render();
});
render();BOM 的核心是把"浏览器"当成可编程对象:location 管地址、history 管导航、navigator 管能力、window 管全局。开发时多使用能力检测("geolocation" in navigator)而非 UA 字符串判断,弹窗与对话框注意浏览器的拦截与阻塞限制,就能写出健壮的浏览器交互代码。