BOM 介绍与概述:浏览器对象模型完全指南
什么是 BOM
当你在浏览器中打开一个网页时,不仅仅是看到页面内容那么简单。浏览器还提供了一整套"控制面板",让你的 JavaScript 代码可以与浏览器进行交互——改变窗口大小、获取浏览器信息、控制页面跳转、操作浏览历史等等。这个"控制面板"就是 BOM(Browser Object Model,浏览器对象模型)。
BOM 是 JavaScript 与浏览器环境交互的桥梁。它提供了一系列对象,让你可以:
- 控制浏览器窗口的行为(打开、关闭、调整大小)
- 获取浏览器和操作系统的信息
- 操作 URL 和浏览历史
- 检测用户的屏幕信息
- 与用户进行交互(弹窗、提示)
与 DOM(Document Object Model,文档对象模型)关注页面内容不同,BOM 关注的是浏览器本身。如果把浏览器比作一辆汽车,DOM 就像是车厢内部的座椅、仪表盘等可见部分,而 BOM 则是方向盘、油门、刹车这些控制汽车的机制。
BOM 与 DOM 的区别
很多初学者容易混淆 BOM 和 DOM,让我们用一个具体的例子来理解它们的区别:
// BOM - 控制浏览器窗口
window.innerWidth; // 获取浏览器窗口宽度
window.location.href = "https://example.com"; // 跳转到新页面
window.history.back(); // 返回上一页
// DOM - 操作页面内容
document.getElementById("title"); // 获取页面元素
document.createElement("div"); // 创建页面元素
document.body.style.backgroundColor = "blue"; // 修改页面样式核心区别:
关注点不同:
- BOM 关注浏览器环境(窗口、地址栏、历史记录、浏览器信息)
- DOM 关注页面内容(HTML 元素、样式、文本)
作用域不同:
- BOM 操作的是浏览器级别的功能
- DOM 操作的是文档级别的内容
标准化程度:
- BOM 没有统一的官方标准,主要由浏览器厂商实现
- DOM 有 W3C 标准规范
关系:
document对象既是 BOM 的一部分(通过window.document访问),也是 DOM 的入口点
实际开发中,BOM 和 DOM 通常配合使用:
// 结合 BOM 和 DOM 的实际场景
function createResponsiveLayout() {
// 使用 BOM 获取窗口宽度
const windowWidth = window.innerWidth;
// 使用 DOM 修改页面布局
const container = document.querySelector(".container");
if (windowWidth < 768) {
container.classList.add("mobile-layout");
} else {
container.classList.add("desktop-layout");
}
}
// 监听窗口大小变化(BOM 事件)
window.addEventListener("resize", createResponsiveLayout);BOM 的核心组成
BOM 由一系列对象组成,这些对象构成了一个层次结构,window 对象位于这个层次结构的顶端。
BOM 对象层次结构
window (全局对象/浏览器窗口)
├── document (文档对象,DOM 的入口)
├── navigator (浏览器信息对象)
├── screen (屏幕信息对象)
├── location (URL 对象)
├── history (历史记录对象)
├── localStorage (本地存储)
├── sessionStorage (会话存储)
└── console (控制台对象)让我们逐一了解这些核心对象:
1. Window 对象
window 对象是 BOM 的核心,它有双重身份:
作为全局对象:所有全局变量和函数都是 window 对象的属性和方法
// 全局变量实际上是 window 的属性
var userName = "John";
console.log(window.userName); // 'John'
// 全局函数实际上是 window 的方法
function greet() {
console.log("Hello!");
}
window.greet(); // 'Hello!'作为浏览器窗口对象:提供控制和查询浏览器窗口的接口
// 获取窗口尺寸
console.log(window.innerWidth); // 视口宽度
console.log(window.innerHeight); // 视口高度
// 打开新窗口
window.open("https://example.com", "_blank");
// 关闭当前窗口
window.close();2. Navigator 对象
navigator 对象包含浏览器的各种信息:
// 浏览器信息
console.log(navigator.userAgent); // 浏览器用户代理字符串
console.log(navigator.platform); // 操作系统平台
console.log(navigator.language); // 浏览器语言
// 在线状态
console.log(navigator.onLine); // true/false
// 检测是否支持 Cookies
console.log(navigator.cookieEnabled); // true/false实际应用:检测移动设备
function isMobileDevice() {
const userAgent = navigator.userAgent.toLowerCase();
const mobileKeywords = ["android", "iphone", "ipad", "mobile"];
return mobileKeywords.some((keyword) => userAgent.includes(keyword));
}
if (isMobileDevice()) {
console.log("用户正在使用移动设备");
} else {
console.log("用户正在使用桌面设备");
}3. Screen 对象
screen 对象包含用户屏幕的信息:
// 屏幕尺寸
console.log(screen.width); // 屏幕宽度(像素)
console.log(screen.height); // 屏幕高度(像素)
// 可用屏幕尺寸(排除任务栏等)
console.log(screen.availWidth); // 可用宽度
console.log(screen.availHeight); // 可用高度
// 颜色深度
console.log(screen.colorDepth); // 颜色位数
console.log(screen.pixelDepth); // 像素深度实际应用:屏幕适配提示
function checkScreenSize() {
if (screen.width < 1024) {
console.log("您的屏幕分辨率较低,建议使用更大的屏幕以获得最佳体验");
}
if (screen.colorDepth < 24) {
console.log("您的屏幕颜色深度较低,可能影响显示效果");
}
}4. Location 对象
location 对象包含当前页面的 URL 信息:
// 完整 URL
console.log(location.href);
// 'https://example.com:8080/path/page.html?id=123#section'
// 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); // '/path/page.html'
console.log(location.search); // '?id=123'
console.log(location.hash); // '#section'页面导航方法:
// 跳转到新页面(可以后退)
location.assign("https://example.com");
// 替换当前页面(不能后退)
location.replace("https://example.com");
// 重新加载页面
location.reload(); // 可能从缓存加载
location.reload(true); // 强制从服务器加载5. History 对象
history 对象用于操作浏览器的历史记录:
// 历史记录长度
console.log(history.length); // 当前会话的历史记录数量
// 导航方法
history.back(); // 后退一页,相当于浏览器的后退按钮
history.forward(); // 前进一页,相当于浏览器的前进按钮
history.go(-1); // 后退一页
history.go(1); // 前进一页
history.go(-2); // 后退两页HTML5 History API:
// 添加历史记录(不刷新页面)
history.pushState(
{ page: 1 }, // 状态对象
"Page 1", // 标题(大多数浏览器忽略此参数)
"/page1" // URL
);
// 替换当前历史记录
history.replaceState({ page: 2 }, "Page 2", "/page2");
// 监听历史记录变化
window.addEventListener("popstate", (event) => {
console.log("历史记录改变:", event.state);
});6. Document 对象
虽然 document 对象主要用于 DOM 操作,但它也是 BOM 的一部分,通过 window.document 访问:
// 文档信息
console.log(document.title); // 页面标题
console.log(document.URL); // 当前 URL
console.log(document.domain); // 域名
console.log(document.referrer); // 来源页面 URL
// 文档状态
console.log(document.readyState); // 'loading', 'interactive', 'complete'
// 文档元素
console.log(document.documentElement); // <html> 元素
console.log(document.head); // <head> 元素
console.log(document.body); // <body> 元素BOM 的浏览器兼容性
BOM 的一个重要特点是缺乏统一的标准,这意味着不同浏览器的实现可能存在差异。不过,主流浏览器对常用的 BOM 功能都有良好支持。
兼容性考虑
- 核心对象(window, navigator, location, history):所有现代浏览器都支持
- HTML5 新增功能(localStorage, sessionStorage, pushState):IE10+ 及所有现代浏览器支持
- 某些特定方法可能存在差异:需要查阅浏览器兼容性文档
兼容性检测最佳实践
// 特性检测而非浏览器检测
function supportsLocalStorage() {
try {
return "localStorage" in window && window.localStorage !== null;
} catch (e) {
return false;
}
}
function supportsGeolocation() {
return "geolocation" in navigator;
}
function supportsHistoryAPI() {
return !!(window.history && history.pushState);
}
// 使用示例
if (supportsLocalStorage()) {
localStorage.setItem("key", "value");
} else {
console.log("LocalStorage 不可用,使用替代方案");
// 使用 Cookie 或其他替代方案
}
if (supportsGeolocation()) {
navigator.geolocation.getCurrentPosition(
(position) => {
console.log("位置:", position.coords);
},
(error) => {
console.log("获取位置失败:", error);
}
);
}跨浏览器兼容工具函数
// 获取视口尺寸(兼容旧版浏览器)
function getViewportSize() {
return {
width:
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth,
height:
window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight,
};
}
// 获取滚动位置(兼容旧版浏览器)
function getScrollPosition() {
return {
x:
window.pageXOffset ||
document.documentElement.scrollLeft ||
document.body.scrollLeft,
y:
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop,
};
}
// 使用示例
const viewport = getViewportSize();
console.log(`视口尺寸: ${viewport.width} x ${viewport.height}`);
const scroll = getScrollPosition();
console.log(`滚动位置: (${scroll.x}, ${scroll.y})`);BOM 的实际应用场景
让我们看看 BOM 在实际开发中的常见应用。
1. 响应式布局检测
class ResponsiveManager {
constructor() {
this.breakpoints = {
mobile: 768,
tablet: 1024,
desktop: 1440,
};
this.init();
}
init() {
// 初始检测
this.handleResize();
// 监听窗口大小变化
window.addEventListener("resize", () => {
this.handleResize();
});
}
handleResize() {
const width = window.innerWidth;
let deviceType;
if (width < this.breakpoints.mobile) {
deviceType = "mobile";
} else if (width < this.breakpoints.tablet) {
deviceType = "tablet";
} else if (width < this.breakpoints.desktop) {
deviceType = "desktop";
} else {
deviceType = "large-desktop";
}
document.body.setAttribute("data-device", deviceType);
console.log(`当前设备类型: ${deviceType}, 宽度: ${width}px`);
}
getCurrentDeviceType() {
return document.body.getAttribute("data-device");
}
}
// 使用
const responsiveManager = new ResponsiveManager();2. 页面跳转与参数传递
class URLManager {
// 解析 URL 参数
static parseQueryString(url = window.location.href) {
const queryString = url.split("?")[1];
if (!queryString) {
return {};
}
const params = {};
const pairs = queryString.split("&");
pairs.forEach((pair) => {
const [key, value] = pair.split("=");
params[decodeURIComponent(key)] = decodeURIComponent(value || "");
});
return params;
}
// 构建 URL 参数
static buildQueryString(params) {
return Object.keys(params)
.map(
(key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`
)
.join("&");
}
// 添加或更新 URL 参数
static updateQueryParams(params) {
const currentParams = this.parseQueryString();
const newParams = { ...currentParams, ...params };
const queryString = this.buildQueryString(newParams);
const newURL = `${window.location.pathname}?${queryString}`;
// 使用 History API 更新 URL(不刷新页面)
if (window.history && history.pushState) {
history.pushState(null, "", newURL);
} else {
// 降级方案:使用 hash
window.location.hash = queryString;
}
}
// 跳转到新页面并携带参数
static navigateWithParams(url, params) {
const queryString = this.buildQueryString(params);
window.location.href = `${url}?${queryString}`;
}
}
// 使用示例
// 获取当前页面的参数
const params = URLManager.parseQueryString();
console.log("URL 参数:", params);
// 假设 URL 是 ?id=123&name=John
// 输出: { id: '123', name: 'John' }
// 更新 URL 参数(不刷新页面)
URLManager.updateQueryParams({ page: 2, sort: "date" });
// 跳转到新页面并携带参数
URLManager.navigateWithParams("/products", {
category: "electronics",
price: "100-500",
});3. 浏览器信息收集(用于分析)
class BrowserAnalytics {
static collectInfo() {
return {
// 浏览器信息
browser: {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
cookieEnabled: navigator.cookieEnabled,
onLine: navigator.onLine,
},
// 屏幕信息
screen: {
width: screen.width,
height: screen.height,
availWidth: screen.availWidth,
availHeight: screen.availHeight,
colorDepth: screen.colorDepth,
pixelDepth: screen.pixelDepth,
},
// 窗口信息
window: {
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
},
// 页面信息
page: {
url: location.href,
protocol: location.protocol,
host: location.host,
pathname: location.pathname,
search: location.search,
hash: location.hash,
referrer: document.referrer,
title: document.title,
},
// 时间戳
timestamp: new Date().toISOString(),
};
}
static sendToAnalytics() {
const info = this.collectInfo();
// 模拟发送到分析服务器
console.log("发送分析数据:", info);
// 实际应用中,可以使用 fetch 发送数据
/*
fetch('/api/analytics', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(info)
});
*/
}
static detectDeviceType() {
const userAgent = navigator.userAgent.toLowerCase();
if (/mobile|android|iphone|ipad|phone/i.test(userAgent)) {
return "mobile";
} else if (/tablet|ipad/i.test(userAgent)) {
return "tablet";
} else {
return "desktop";
}
}
static getBrowserName() {
const userAgent = navigator.userAgent;
let browserName;
if (userAgent.indexOf("Firefox") > -1) {
browserName = "Firefox";
} else if (userAgent.indexOf("Chrome") > -1) {
browserName = "Chrome";
} else if (userAgent.indexOf("Safari") > -1) {
browserName = "Safari";
} else if (userAgent.indexOf("Edge") > -1) {
browserName = "Edge";
} else if (
userAgent.indexOf("MSIE") > -1 ||
userAgent.indexOf("Trident/") > -1
) {
browserName = "Internet Explorer";
} else {
browserName = "Unknown";
}
return browserName;
}
}
// 使用
console.log("设备类型:", BrowserAnalytics.detectDeviceType());
console.log("浏览器名称:", BrowserAnalytics.getBrowserName());
BrowserAnalytics.sendToAnalytics();4. 单页应用路由管理
class SimpleRouter {
constructor(routes) {
this.routes = routes; // { '/home': handlerFunction, '/about': handlerFunction }
this.init();
}
init() {
// 监听浏览器前进/后退按钮
window.addEventListener("popstate", (event) => {
this.handleRoute(window.location.pathname);
});
// 监听链接点击
document.addEventListener("click", (event) => {
if (event.target.matches("[data-link]")) {
event.preventDefault();
const path = event.target.getAttribute("href");
this.navigate(path);
}
});
// 初始路由
this.handleRoute(window.location.pathname);
}
navigate(path) {
// 更新浏览器历史记录
history.pushState(null, "", path);
// 处理路由
this.handleRoute(path);
}
handleRoute(path) {
const handler = this.routes[path] || this.routes["/404"];
if (handler) {
handler();
} else {
console.error(`找不到路由: ${path}`);
}
}
}
// 使用示例
const router = new SimpleRouter({
"/": () => {
console.log("首页");
document.getElementById("app").innerHTML = "<h1>首页</h1>";
},
"/about": () => {
console.log("关于页");
document.getElementById("app").innerHTML = "<h1>关于我们</h1>";
},
"/contact": () => {
console.log("联系页");
document.getElementById("app").innerHTML = "<h1>联系我们</h1>";
},
"/404": () => {
console.log("404 页面");
document.getElementById("app").innerHTML = "<h1>页面未找到</h1>";
},
});
// 编程式导航
// router.navigate('/about');5. 页面可见性检测
class VisibilityManager {
constructor(callbacks = {}) {
this.callbacks = callbacks;
this.init();
}
init() {
// 监听页面可见性变化
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
console.log("页面隐藏");
this.callbacks.onHidden?.();
} else {
console.log("页面可见");
this.callbacks.onVisible?.();
}
});
// 监听窗口焦点
window.addEventListener("blur", () => {
console.log("窗口失去焦点");
this.callbacks.onBlur?.();
});
window.addEventListener("focus", () => {
console.log("窗口获得焦点");
this.callbacks.onFocus?.();
});
}
isPageVisible() {
return !document.hidden;
}
}
// 使用示例:视频自动暂停/播放
const visibilityManager = new VisibilityManager({
onHidden: () => {
const video = document.querySelector("video");
if (video && !video.paused) {
video.pause();
video.dataset.autoPaused = "true";
}
},
onVisible: () => {
const video = document.querySelector("video");
if (video && video.dataset.autoPaused === "true") {
video.play();
delete video.dataset.autoPaused;
}
},
});BOM 使用的最佳实践
1. 避免过度依赖全局 window 对象
// ❌ 不好的做法
function checkWidth() {
if (window.innerWidth > 1024) {
// 在函数内部直接使用 window
return true;
}
}
// ✅ 更好的做法
function checkWidth(windowObj = window) {
// 将 window 作为参数传入
if (windowObj.innerWidth > 1024) {
return true;
}
}
// 这样做的好处:
// 1. 函数更易测试(可以传入模拟的 window 对象)
// 2. 依赖更明确
// 3. 在非浏览器环境(如 Node.js)中更安全2. 防抖和节流窗口事件
// 防抖函数
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流函数
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// 使用防抖处理窗口调整大小
const handleResize = debounce(() => {
console.log("窗口大小:", window.innerWidth, window.innerHeight);
}, 250);
window.addEventListener("resize", handleResize);
// 使用节流处理滚动事件
const handleScroll = throttle(() => {
console.log("滚动位置:", window.pageYOffset);
}, 100);
window.addEventListener("scroll", handleScroll);3. 优雅地处理 History API
class NavigationGuard {
static confirmLeave(message = "确定要离开此页面吗?未保存的更改将丢失。") {
window.addEventListener("beforeunload", (event) => {
// 检查是否有未保存的更改
if (this.hasUnsavedChanges()) {
event.preventDefault();
event.returnValue = message; // 某些浏览器需要设置 returnValue
return message;
}
});
}
static hasUnsavedChanges() {
// 实际应用中,检查表单或编辑器状态
return window.hasUnsavedData === true;
}
}
// 使用
NavigationGuard.confirmLeave();小结
BOM(浏览器对象模型)是 JavaScript 与浏览器环境交互的关键接口。它提供了一系列对象,让我们可以:
- 通过
window对象控制浏览器窗口和全局作用域 - 通过
navigator对象获取浏览器和系统信息 - 通过
screen对象了解用户屏幕特性 - 通过
location对象操作 URL 和页面导航 - 通过
history对象管理浏览历史记录
核心要点:
- BOM vs DOM:BOM 关注浏览器环境,DOM 关注页面内容
- 浏览器兼容性:使用特性检测而非浏览器检测
- 实际应用:响应式设计、路由管理、页面分析、用户体验优化
- 最佳实践:防抖节流、优雅降级、明确依赖
虽然 BOM 没有统一的官方标准,但它已成为现代 Web 开发不可或缺的一部分。掌握 BOM,你就掌握了与浏览器"对话"的能力,能够创建更智能、更友好的 Web 应用。