Skip to content

BOM 介绍与概述:浏览器对象模型完全指南

什么是 BOM

当你在浏览器中打开一个网页时,不仅仅是看到页面内容那么简单。浏览器还提供了一整套"控制面板",让你的 JavaScript 代码可以与浏览器进行交互——改变窗口大小、获取浏览器信息、控制页面跳转、操作浏览历史等等。这个"控制面板"就是 BOM(Browser Object Model,浏览器对象模型)。

BOM 是 JavaScript 与浏览器环境交互的桥梁。它提供了一系列对象,让你可以:

  • 控制浏览器窗口的行为(打开、关闭、调整大小)
  • 获取浏览器和操作系统的信息
  • 操作 URL 和浏览历史
  • 检测用户的屏幕信息
  • 与用户进行交互(弹窗、提示)

与 DOM(Document Object Model,文档对象模型)关注页面内容不同,BOM 关注的是浏览器本身。如果把浏览器比作一辆汽车,DOM 就像是车厢内部的座椅、仪表盘等可见部分,而 BOM 则是方向盘、油门、刹车这些控制汽车的机制。

BOM 与 DOM 的区别

很多初学者容易混淆 BOM 和 DOM,让我们用一个具体的例子来理解它们的区别:

javascript
// BOM - 控制浏览器窗口
window.innerWidth; // 获取浏览器窗口宽度
window.location.href = "https://example.com"; // 跳转到新页面
window.history.back(); // 返回上一页

// DOM - 操作页面内容
document.getElementById("title"); // 获取页面元素
document.createElement("div"); // 创建页面元素
document.body.style.backgroundColor = "blue"; // 修改页面样式

核心区别:

  1. 关注点不同

    • BOM 关注浏览器环境(窗口、地址栏、历史记录、浏览器信息)
    • DOM 关注页面内容(HTML 元素、样式、文本)
  2. 作用域不同

    • BOM 操作的是浏览器级别的功能
    • DOM 操作的是文档级别的内容
  3. 标准化程度

    • BOM 没有统一的官方标准,主要由浏览器厂商实现
    • DOM 有 W3C 标准规范
  4. 关系

    • document 对象既是 BOM 的一部分(通过 window.document 访问),也是 DOM 的入口点

实际开发中,BOM 和 DOM 通常配合使用:

javascript
// 结合 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 对象的属性和方法

javascript
// 全局变量实际上是 window 的属性
var userName = "John";
console.log(window.userName); // 'John'

// 全局函数实际上是 window 的方法
function greet() {
  console.log("Hello!");
}

window.greet(); // 'Hello!'

作为浏览器窗口对象:提供控制和查询浏览器窗口的接口

javascript
// 获取窗口尺寸
console.log(window.innerWidth); // 视口宽度
console.log(window.innerHeight); // 视口高度

// 打开新窗口
window.open("https://example.com", "_blank");

// 关闭当前窗口
window.close();

2. Navigator 对象

navigator 对象包含浏览器的各种信息:

javascript
// 浏览器信息
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

实际应用:检测移动设备

javascript
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 对象包含用户屏幕的信息:

javascript
// 屏幕尺寸
console.log(screen.width); // 屏幕宽度(像素)
console.log(screen.height); // 屏幕高度(像素)

// 可用屏幕尺寸(排除任务栏等)
console.log(screen.availWidth); // 可用宽度
console.log(screen.availHeight); // 可用高度

// 颜色深度
console.log(screen.colorDepth); // 颜色位数
console.log(screen.pixelDepth); // 像素深度

实际应用:屏幕适配提示

javascript
function checkScreenSize() {
  if (screen.width < 1024) {
    console.log("您的屏幕分辨率较低,建议使用更大的屏幕以获得最佳体验");
  }

  if (screen.colorDepth < 24) {
    console.log("您的屏幕颜色深度较低,可能影响显示效果");
  }
}

4. Location 对象

location 对象包含当前页面的 URL 信息:

javascript
// 完整 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'

页面导航方法:

javascript
// 跳转到新页面(可以后退)
location.assign("https://example.com");

// 替换当前页面(不能后退)
location.replace("https://example.com");

// 重新加载页面
location.reload(); // 可能从缓存加载
location.reload(true); // 强制从服务器加载

5. History 对象

history 对象用于操作浏览器的历史记录:

javascript
// 历史记录长度
console.log(history.length); // 当前会话的历史记录数量

// 导航方法
history.back(); // 后退一页,相当于浏览器的后退按钮
history.forward(); // 前进一页,相当于浏览器的前进按钮
history.go(-1); // 后退一页
history.go(1); // 前进一页
history.go(-2); // 后退两页

HTML5 History API:

javascript
// 添加历史记录(不刷新页面)
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 访问:

javascript
// 文档信息
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 功能都有良好支持。

兼容性考虑

  1. 核心对象(window, navigator, location, history):所有现代浏览器都支持
  2. HTML5 新增功能(localStorage, sessionStorage, pushState):IE10+ 及所有现代浏览器支持
  3. 某些特定方法可能存在差异:需要查阅浏览器兼容性文档

兼容性检测最佳实践

javascript
// 特性检测而非浏览器检测
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);
    }
  );
}

跨浏览器兼容工具函数

javascript
// 获取视口尺寸(兼容旧版浏览器)
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. 响应式布局检测

javascript
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. 页面跳转与参数传递

javascript
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. 浏览器信息收集(用于分析)

javascript
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. 单页应用路由管理

javascript
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. 页面可见性检测

javascript
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 对象

javascript
// ❌ 不好的做法
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. 防抖和节流窗口事件

javascript
// 防抖函数
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

javascript
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 对象管理浏览历史记录

核心要点:

  1. BOM vs DOM:BOM 关注浏览器环境,DOM 关注页面内容
  2. 浏览器兼容性:使用特性检测而非浏览器检测
  3. 实际应用:响应式设计、路由管理、页面分析、用户体验优化
  4. 最佳实践:防抖节流、优雅降级、明确依赖

虽然 BOM 没有统一的官方标准,但它已成为现代 Web 开发不可或缺的一部分。掌握 BOM,你就掌握了与浏览器"对话"的能力,能够创建更智能、更友好的 Web 应用。