DOM操作与浏览器模型
本文介绍 JavaScript 在浏览器环境中的核心能力:DOM(文档对象模型)操作和 BOM(浏览器对象模型)。这些 API 是前端开发的基础,掌握它们能够实现动态网页交互、用户界面操作和浏览器功能调用。
# 一、DOM 简介
DOM(Document Object Model,文档对象模型)是 HTML 和 XML 文档的编程接口。浏览器将 HTML 文档解析为一个树形结构,JavaScript 通过 DOM API 可以访问和操作这个树。
# DOM 树结构
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1 id="title">Hello World</h1>
<p class="text">This is a paragraph.</p>
</body>
</html>
DOM 树表示:
Document
└── html
├── head
│ └── title
│ └── "Example"
└── body
├── h1#title
│ └── "Hello World"
└── p.text
└── "This is a paragraph."
# 节点类型
DOM 中的每个元素都是一个节点(Node),常见节点类型:
- Element(元素节点):
<div>、<p>等 HTML 标签 - Text(文本节点):标签内的文本内容
- Comment(注释节点):
<!-- 注释 --> - Document(文档节点):整个文档的根节点
# 二、选择 DOM 元素
# 基本选择方法
// 通过 ID 选择(返回单个元素)
const title = document.getElementById('title');
// 通过类名选择(返回 HTMLCollection)
const texts = document.getElementsByClassName('text');
// 通过标签名选择(返回 HTMLCollection)
const paragraphs = document.getElementsByTagName('p');
// 通过 name 属性选择(常用于表单)
const inputs = document.getElementsByName('username');
# 现代选择器(推荐)
// querySelector(返回第一个匹配元素)
const title = document.querySelector('#title');
const firstText = document.querySelector('.text');
const firstDiv = document.querySelector('div');
// querySelectorAll(返回所有匹配元素,NodeList)
const allTexts = document.querySelectorAll('.text');
const allDivs = document.querySelectorAll('div');
// 支持复杂 CSS 选择器
const link = document.querySelector('div > a.active[href^="https"]');
# HTMLCollection vs NodeList
// HTMLCollection(动态集合)
const divs = document.getElementsByTagName('div');
console.log(divs.length); // 假设为 3
document.body.appendChild(document.createElement('div'));
console.log(divs.length); // 自动变为 4
// NodeList(querySelectorAll 返回的是静态集合)
const divs2 = document.querySelectorAll('div');
console.log(divs2.length); // 假设为 4
document.body.appendChild(document.createElement('div'));
console.log(divs2.length); // 仍然是 4
// 转换为数组
const divsArray = Array.from(divs);
const divsArray2 = [...divs2];
# 三、操作 DOM 元素
# 修改内容
const div = document.querySelector('div');
// innerHTML(解析 HTML 标签)
div.innerHTML = '<strong>Bold Text</strong>';
// textContent(纯文本,不解析 HTML)
div.textContent = '<strong>Not Bold</strong>'; // 显示为字符串
// innerText(考虑样式,如 display: none)
div.innerText = 'Visible Text';
安全提示:使用 innerHTML 插入用户输入时,需防止 XSS 攻击。推荐使用 textContent 或 DOM 方法。
# 修改属性
const img = document.querySelector('img');
// 标准属性
img.src = 'new-image.jpg';
img.alt = 'New Image';
// getAttribute / setAttribute
img.setAttribute('data-id', '123');
const dataId = img.getAttribute('data-id');
// 删除属性
img.removeAttribute('data-id');
// 自定义数据属性(dataset)
img.dataset.userId = '456'; // <img data-user-id="456">
console.log(img.dataset.userId); // '456'
# 修改样式
const box = document.querySelector('.box');
// 内联样式
box.style.color = 'red';
box.style.backgroundColor = 'blue'; // 驼峰命名
box.style.fontSize = '20px';
// 批量设置
box.style.cssText = 'color: red; background-color: blue; font-size: 20px;';
// 获取计算样式(包含继承和默认样式)
const styles = window.getComputedStyle(box);
console.log(styles.color); // 'rgb(255, 0, 0)'
# 修改类名
const div = document.querySelector('div');
// className(字符串)
div.className = 'box active';
// classList(推荐)
div.classList.add('highlight');
div.classList.remove('active');
div.classList.toggle('hidden'); // 切换(有则删除,无则添加)
div.classList.contains('box'); // true
div.classList.replace('box', 'container');
# 四、创建和插入元素
# 创建元素
// 创建元素节点
const div = document.createElement('div');
div.textContent = 'New Div';
div.className = 'box';
// 创建文本节点
const text = document.createTextNode('Hello');
// 创建文档片段(性能优化)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
document.querySelector('ul').appendChild(fragment);
# 插入元素
const parent = document.querySelector('#parent');
const newDiv = document.createElement('div');
// appendChild(末尾添加)
parent.appendChild(newDiv);
// insertBefore(指定位置插入)
const referenceNode = document.querySelector('#child');
parent.insertBefore(newDiv, referenceNode);
// 现代方法(推荐)
parent.append(newDiv); // 可添加多个节点或字符串
parent.prepend(newDiv); // 开头插入
referenceNode.before(newDiv); // 在参考节点之前插入
referenceNode.after(newDiv); // 在参考节点之后插入
referenceNode.replaceWith(newDiv); // 替换节点
# 删除元素
const div = document.querySelector('div');
// 传统方法
div.parentNode.removeChild(div);
// 现代方法(推荐)
div.remove();
# 克隆元素
const original = document.querySelector('.original');
// 浅克隆(不包含子元素)
const clone1 = original.cloneNode(false);
// 深克隆(包含所有子元素)
const clone2 = original.cloneNode(true);
document.body.appendChild(clone2);
# 五、遍历 DOM 树
# 父子关系
const child = document.querySelector('.child');
// 父节点
child.parentNode;
child.parentElement; // 推荐
// 子节点
const parent = document.querySelector('.parent');
parent.childNodes; // 所有节点(包括文本、注释)
parent.children; // 仅元素节点(推荐)
parent.firstChild; // 第一个节点(可能是文本节点)
parent.firstElementChild; // 第一个元素节点(推荐)
parent.lastElementChild; // 最后一个元素节点
# 兄弟关系
const element = document.querySelector('.element');
// 兄弟节点
element.nextSibling; // 下一个节点(可能是文本节点)
element.nextElementSibling; // 下一个元素节点(推荐)
element.previousElementSibling; // 上一个元素节点
# 遍历示例
// 递归遍历所有元素
function traverseDOM(node) {
console.log(node);
for (let child of node.children) {
traverseDOM(child);
}
}
traverseDOM(document.body);
# 六、事件处理
# 添加事件监听
const button = document.querySelector('button');
// addEventListener(推荐)
button.addEventListener('click', function(event) {
console.log('Button clicked!', event);
});
// 箭头函数
button.addEventListener('click', (event) => {
console.log('Clicked!', event);
});
// 事件处理器属性(不推荐,只能绑定一个处理函数)
button.onclick = function() {
console.log('Clicked!');
};
# 事件对象
button.addEventListener('click', (event) => {
event.target; // 触发事件的元素
event.currentTarget; // 绑定事件的元素
event.type; // 事件类型('click')
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
});
# 常用事件类型
// 鼠标事件
element.addEventListener('click', handler); // 单击
element.addEventListener('dblclick', handler); // 双击
element.addEventListener('mousedown', handler); // 鼠标按下
element.addEventListener('mouseup', handler); // 鼠标释放
element.addEventListener('mousemove', handler); // 鼠标移动
element.addEventListener('mouseenter', handler); // 鼠标进入(不冒泡)
element.addEventListener('mouseleave', handler); // 鼠标离开(不冒泡)
element.addEventListener('mouseover', handler); // 鼠标悬停(冒泡)
// 键盘事件
element.addEventListener('keydown', handler); // 按键按下
element.addEventListener('keyup', handler); // 按键释放
element.addEventListener('keypress', handler); // 按键(已弃用)
// 表单事件
form.addEventListener('submit', handler); // 表单提交
input.addEventListener('input', handler); // 输入变化
input.addEventListener('change', handler); // 值改变后失焦
input.addEventListener('focus', handler); // 获得焦点
input.addEventListener('blur', handler); // 失去焦点
// 文档事件
document.addEventListener('DOMContentLoaded', handler); // DOM 加载完成
window.addEventListener('load', handler); // 页面完全加载
window.addEventListener('beforeunload', handler); // 页面卸载前
# 事件冒泡与捕获
// 事件流:捕获阶段 → 目标阶段 → 冒泡阶段
// 冒泡(默认)
parent.addEventListener('click', () => console.log('Parent'));
child.addEventListener('click', () => console.log('Child'));
// 点击 child:输出 Child → Parent
// 捕获(第三个参数为 true)
parent.addEventListener('click', () => console.log('Parent'), true);
child.addEventListener('click', () => console.log('Child'), true);
// 点击 child:输出 Parent → Child
// 阻止冒泡
child.addEventListener('click', (event) => {
event.stopPropagation();
console.log('Child');
});
// 点击 child:仅输出 Child
# 事件委托
利用事件冒泡,在父元素上监听子元素事件:
const ul = document.querySelector('ul');
// 为所有 li 添加点击事件(包括动态添加的)
ul.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Clicked:', event.target.textContent);
}
});
// 动态添加 li,事件自动生效
const newLi = document.createElement('li');
newLi.textContent = 'New Item';
ul.appendChild(newLi);
# 移除事件监听
function handleClick() {
console.log('Clicked!');
}
button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick); // 必须是同一个函数引用
// 匿名函数无法移除
button.addEventListener('click', () => console.log('Clicked!')); // 无法移除
# 事件选项
button.addEventListener('click', handler, {
once: true, // 只触发一次
passive: true, // 不调用 preventDefault(提升滚动性能)
capture: false // 冒泡阶段触发
});
# 七、表单处理
# 获取表单值
const form = document.querySelector('form');
const input = document.querySelector('input[name="username"]');
const select = document.querySelector('select');
const checkbox = document.querySelector('input[type="checkbox"]');
// 获取值
console.log(input.value);
console.log(select.value);
console.log(checkbox.checked); // true/false
# 表单验证
form.addEventListener('submit', (event) => {
event.preventDefault(); // 阻止默认提交
const username = form.elements.username.value;
const email = form.elements.email.value;
if (username.trim() === '') {
alert('Username is required');
return;
}
if (!/\S+@\S+\.\S+/.test(email)) {
alert('Invalid email');
return;
}
// 提交表单数据
const formData = new FormData(form);
console.log(Object.fromEntries(formData));
});
# FormData API
const formData = new FormData(form);
// 添加字段
formData.append('age', '30');
// 获取字段
formData.get('username');
// 遍历
for (let [key, value] of formData) {
console.log(key, value);
}
// 发送到服务器
fetch('/api/submit', {
method: 'POST',
body: formData
});
# 八、浏览器对象模型(BOM)
BOM(Browser Object Model)提供与浏览器交互的对象和方法,核心是 window 对象。
# window 对象
// 窗口尺寸
window.innerWidth; // 视口宽度(包含滚动条)
window.innerHeight; // 视口高度
window.outerWidth; // 浏览器窗口宽度
window.outerHeight; // 浏览器窗口高度
// 滚动
window.scrollX; // 水平滚动距离
window.scrollY; // 垂直滚动距离
window.scrollTo(0, 100); // 滚动到指定位置
window.scrollBy(0, 50); // 相对滚动
element.scrollIntoView(); // 滚动到元素可见
// 打开/关闭窗口
const newWindow = window.open('https://example.com', '_blank');
newWindow.close();
// 对话框
alert('Alert message');
const confirmed = confirm('Are you sure?');
const userInput = prompt('Enter your name:');
# location 对象
// 当前 URL 信息
location.href; // 完整 URL
location.protocol; // 'https:'
location.host; // 'example.com:8080'
location.hostname; // 'example.com'
location.port; // '8080'
location.pathname; // '/path/to/page'
location.search; // '?query=value'
location.hash; // '#section'
// URL 操作
location.assign('https://example.com'); // 跳转(可后退)
location.replace('https://example.com'); // 替换(不可后退)
location.reload(); // 刷新页面
# history 对象
// 浏览历史操作
history.back(); // 后退
history.forward(); // 前进
history.go(-2); // 后退两步
history.go(1); // 前进一步
// 历史记录长度
history.length;
// HTML5 History API
history.pushState({ page: 1 }, 'Title', '/page1');
history.replaceState({ page: 2 }, 'Title', '/page2');
window.addEventListener('popstate', (event) => {
console.log('State:', event.state);
});
# navigator 对象
// 浏览器信息
navigator.userAgent; // 用户代理字符串
navigator.language; // 语言('zh-CN')
navigator.languages; // 语言列表
navigator.platform; // 操作系统平台
navigator.onLine; // 是否在线(true/false)
// 地理位置(需要用户授权)
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position.coords.latitude);
console.log(position.coords.longitude);
},
(error) => console.error(error)
);
// 剪贴板(需要用户授权)
navigator.clipboard.writeText('Copied text');
navigator.clipboard.readText().then(text => console.log(text));
# screen 对象
screen.width; // 屏幕宽度
screen.height; // 屏幕高度
screen.availWidth; // 可用宽度(排除任务栏)
screen.availHeight; // 可用高度
screen.colorDepth; // 颜色深度
# 九、定时器
# setTimeout(延迟执行)
// 1 秒后执行
const timeoutId = setTimeout(() => {
console.log('Executed after 1 second');
}, 1000);
// 取消定时器
clearTimeout(timeoutId);
// 传递参数
setTimeout((name) => {
console.log(`Hello, ${name}`);
}, 1000, 'Alice');
# setInterval(重复执行)
// 每秒执行一次
const intervalId = setInterval(() => {
console.log('Executed every second');
}, 1000);
// 5 秒后停止
setTimeout(() => {
clearInterval(intervalId);
}, 5000);
# requestAnimationFrame(动画)
function animate() {
// 动画逻辑
box.style.left = parseInt(box.style.left || 0) + 1 + 'px';
// 递归调用(约 60fps)
requestAnimationFrame(animate);
}
const animationId = requestAnimationFrame(animate);
// 取消动画
cancelAnimationFrame(animationId);
# 十、存储 API
# localStorage(持久化存储)
// 设置
localStorage.setItem('username', 'Alice');
// 获取
const username = localStorage.getItem('username');
// 删除
localStorage.removeItem('username');
// 清空
localStorage.clear();
// 存储对象
const user = { name: 'Alice', age: 30 };
localStorage.setItem('user', JSON.stringify(user));
const storedUser = JSON.parse(localStorage.getItem('user'));
# sessionStorage(会话存储)
// API 与 localStorage 相同,但仅在当前会话有效
sessionStorage.setItem('token', 'abc123');
const token = sessionStorage.getItem('token');
# Cookie
// 设置 Cookie
document.cookie = 'username=Alice; max-age=3600; path=/';
// 读取 Cookie
console.log(document.cookie); // 'username=Alice; other=value'
// 删除 Cookie(设置过期时间为过去)
document.cookie = 'username=; max-age=0';
# 十一、实用技巧
# 防抖(debounce)
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 使用场景:搜索输入
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', debounce((event) => {
console.log('Search:', event.target.value);
}, 500));
# 节流(throttle)
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(this, args);
}
};
}
// 使用场景:滚动监听
window.addEventListener('scroll', throttle(() => {
console.log('Scrolled:', window.scrollY);
}, 200));
# 懒加载图片
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
images.forEach(img => observer.observe(img));
# 检测元素可见性
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is visible');
} else {
console.log('Element is hidden');
}
});
});
observer.observe(document.querySelector('.target'));
# 监听 DOM 变化
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
console.log('DOM changed:', mutation);
});
});
observer.observe(document.body, {
childList: true, // 监听子节点变化
attributes: true, // 监听属性变化
subtree: true // 监听所有后代节点
});
// 停止监听
observer.disconnect();
# 十二、性能优化
# 减少重排和重绘
// ❌ 糟糕:多次修改样式导致多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = 'red';
// ✅ 推荐:批量修改
element.style.cssText = 'width: 100px; height: 100px; background-color: red;';
// ✅ 推荐:使用类名
element.classList.add('box-style');
# 使用文档片段
// ❌ 糟糕:每次添加都触发重排
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
ul.appendChild(li);
}
// ✅ 推荐:使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
ul.appendChild(fragment);
# 避免强制同步布局
// ❌ 糟糕:读取布局属性后立即修改样式
element.style.width = '100px';
const height = element.offsetHeight; // 触发重排
element.style.height = height + 'px';
// ✅ 推荐:先读取,再批量修改
const height = element.offsetHeight;
element.style.width = '100px';
element.style.height = height + 'px';
# 十三、总结
DOM 和 BOM 是前端开发的核心 API,掌握以下要点:
- DOM 操作:选择、创建、修改、删除元素
- 事件处理:addEventListener、事件冒泡、事件委托
- 表单处理:获取值、验证、FormData
- BOM:window、location、history、navigator
- 存储:localStorage、sessionStorage、Cookie
- 性能优化:减少重排重绘、使用文档片段、事件委托
配合 JavaScript 核心语法(详见JavaScript极简入门),即可构建功能完善的动态网页应用。
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/11/07