为什么理解 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();