JavaScript BOM 与 DOM

深入解析浏览器对象模型(BOM)与文档对象模型(DOM):window/navigator/location/history、DOM 树结构、DOM 查询性能对比,以及跨窗口通信机制。

为什么理解 BOM 和 DOM 是前端开发的根基

浏览器环境提供了 JavaScript 操作网页的能力。BOM(Browser Object Model)提供访问浏览器功能的接口,而 DOM(Document Object Model)提供操作网页内容的接口。理解这两个模型,是开发任何网页应用的基础。

这篇文章深入解析 BOM 和 DOM 的核心概念和常见操作。


一、BOM(浏览器对象模型)

1.1 window 对象

window 是浏览器中的全局对象,代表浏览器窗口:

// 全局变量实际是 window 的属性
var name = 'Alice';
console.log(window.name); // 'Alice'

// 全局函数实际是 window 的方法
function greet() { return 'Hi'; }
console.log(window.greet()); // 'Hi'

// window 的常用属性
window.innerHeight; // 视口高度
window.innerWidth;  // 视口宽度
window.scrollY;      // 垂直滚动距离
window.scrollX;      // 水平滚动距离

1.2 navigator 对象

navigator 包含浏览器信息:

console.log(navigator.userAgent); // 'Mozilla/5.0...'
console.log(navigator.language);  // 'zh-CN'
console.log(navigator.onLine);     // true/false

// 检测网络状态
window.addEventListener('online', () => console.log('Network connected'));
window.addEventListener('offline', () => console.log('Network disconnected'));

1.3 location 对象

location 包含当前 URL 信息:

console.log(location.href);     // 'https://example.com/path?query=1'
console.log(location.protocol); // 'https:'
console.log(location.host);     // 'example.com'
console.log(location.pathname); // '/path'
console.log(location.search);   // '?query=1'
console.log(location.hash);     // '#section'

// 导航
location.assign('https://example.com'); // 导航到新 URL,可回退
location.replace('https://example.com'); // 替换当前 URL,不可回退
location.reload(); // 刷新页面

1.4 history 对象

history 管理浏览器历史:

history.back();     // 后退
history.forward();  // 前进
history.go(-1);     // 相对当前位置
history.go(1);      // 相对当前位置

// HTML5 History API
history.pushState({ page: 1 }, 'Title', '/page-1'); // 添加历史记录
history.replaceState({ page: 2 }, 'Title', '/page-2'); // 替换当前历史

// 监听 popstate
window.addEventListener('popstate', (event) => {
  console.log('State:', event.state);
});

二、DOM(文档对象模型)

2.1 DOM 树结构

flowchart TD
    A["Document"] --> B["html"]
    B --> C["head"]
    B --> D["body"]
    C --> E["title"]
    D --> F["div#container"]
    D --> G["script"]
    F --> H["h1"]
    F --> I["p"]

2.2 Node 类型

DOM 中有多种节点类型:

console.log(Node.ELEMENT_NODE); // 1
console.log(Node.TEXT_NODE);    // 3
console.log(Node.COMMENT_NODE); // 8
console.log(Node.DOCUMENT_NODE); // 9

// 检查节点类型
element.nodeType === Node.ELEMENT_NODE;

// 创建节点
document.createElement('div');
document.createTextNode('Hello');
document.createComment('This is a comment');

三、DOM 查询

3.1 查询方法对比

方法 性能 返回值
getElementById O(1) — 直接查找 单个元素
querySelector O(n) — CSS 选择器匹配 单个元素
querySelectorAll O(n) — CSS 选择器匹配 NodeList
getElementsByClassName O(n) — 实时集合 HTMLCollection
getElementsByTagName O(n) — 实时集合 HTMLCollection

3.2 性能最佳实践

// 缓存查询结果
const container = document.getElementById('container');
const items = container.querySelectorAll('.item'); // 一次查询,多次使用

// 避免频繁查询
// 不好:每次循环都查询
for (const div of document.querySelectorAll('div')) {
  div.addEventListener('click', handleClick);
}

// 好:先查询,再使用
const divs = document.querySelectorAll('div');
for (const div of divs) {
  div.addEventListener('click', handleClick);
}

四、DOM 操作

4.1 批量操作(DocumentFragment)

// 频繁修改 DOM 很低效
const container = document.getElementById('list');

// 不好:每次添加都触发重排
for (let i = 0; i < 100; i++) {
  const item = document.createElement('div');
  item.textContent = `Item ${i}`;
  container.appendChild(item);
}

// 好:使用 DocumentFragment 批量添加
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const item = document.createElement('div');
  item.textContent = `Item ${i}`;
  fragment.appendChild(item);
}
container.appendChild(fragment); // 只触发一次重排

4.2 元素操作

// 创建
const div = document.createElement('div');
div.textContent = 'Hello';
div.className = 'container';
div.setAttribute('data-id', '123');

// 添加
parent.appendChild(div); // 末尾添加
parent.insertBefore(div, referenceNode); // 在参考节点前插入

// 删除
div.parentNode.removeChild(div); // 旧方法
div.remove(); // 新方法(IE 不支持)

// 替换
parent.replaceChild(newChild, oldChild);

五、跨窗口通信

5.1 postMessage

// 发送消息(窗口 A)
const iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage('Hello', 'https://example.com');

// 接收消息(窗口 B)
window.addEventListener('message', (event) => {
  // 验证来源
  if (event.origin !== 'https://trusted-site.com') return;

  console.log('Received:', event.data);
  console.log('From:', event.source);
});

5.2 MessageChannel

// 创建 MessageChannel
const channel = new MessageChannel();

// 端口两端
const port1 = channel.port1;
const port2 = channel.port2;

// 一端发送消息
port1.postMessage('Hello from port1');

// 另一端监听
port2.onmessage = (event) => {
  console.log('Received:', event.data);
};

// 开启端口
port1.start();
port2.start();

六、延展阅读