在前端开发中,仅掌握JavaScript语法是远远不够的。真正让页面“活”起来的,是与HTML文档交互的能力。今天我们将深入探讨Web技术的基石之一——DOM(文档对象模型)。理解DOM是您实现动态网页效果、处理用户交互的关键一步。
一、什么是DOM?
1. 核心概念
DOM是浏览器提供的一套API,它将HTML或XML文档解析成一个由对象和节点组成的结构化表示。简单来说,DOM是连接JavaScript代码与网页内容的桥梁,让静态的HTML拥有了被动态操作的可能。
2. DOM树结构
浏览器加载HTML后,会构建一个树形结构,即DOM树。树中的每个部分都是一个节点,主要包括:
- 文档节点(Document):整棵树的根,通过全局对象
document 访问。
- 元素节点(Element):对应HTML标签(如
<div>、<p>),是操作最频繁的节点。
- 文本节点(Text):标签内的纯文本内容。
- 属性节点(Attribute):标签的属性(如
id、class)。
看一个简单的例子,其对应的DOM树关系一目了然:
<!DOCTYPE html>
<html>
<head><title>测试</title></head>
<body>
<div id="box" class="container">
Hello DOM
<p>这是段落</p>
</div>
</body>
</html>
DOM树关系可以这样表示:
document
└── html (元素节点)
├── head (元素节点)
│ └── title (元素节点)
│ └── #text: 测试 (文本节点)
└── body (元素节点)
└── div (元素节点, id="box", class="container")
├── #text: Hello DOM (文本节点)
└── p (元素节点)
└── #text: 这是段落 (文本节点)
3. DOM与BOM的关联
document 对象实际上是BOM(浏览器对象模型)中 window 对象的一个属性,通常简写为 document。它们协同工作,共同完成复杂的网页交互任务。
二、DOM节点介绍
1. 节点类型与属性
每个节点都有几个核心属性,用于识别其类型和信息:
| 属性 |
含义 |
示例(元素节点) |
nodeType |
节点类型(数字):1=元素节点,3=文本节点,9=文档节点 |
div.nodeType = 1 |
nodeName |
节点名称:元素节点为大写标签名,文本节点为 #text |
div.nodeName = ‘DIV’ |
nodeValue |
节点值:文本节点为文本内容,元素节点为 null |
textNode.nodeValue = ‘Hello DOM’ |
2. 节点关系属性
通过以下属性,可以遍历DOM树,访问节点的“家人”:
| 属性 |
含义 |
语法示例 |
parentNode |
获取父节点(唯一) |
p.parentNode → div |
childNodes |
获取所有子节点(含文本、元素节点) |
div.childNodes |
children |
获取所有元素子节点(更常用) |
div.children |
firstChild / lastChild |
获取第一个/最后一个子节点(含文本节点) |
div.firstChild |
previousSibling / nextSibling |
获取前一个/后一个兄弟节点(含文本节点) |
p.previousSibling |
3. 案例演示(节点遍历)
// 假设页面有 <div id="box">Hello<p>段落</p></div>
var box = document.getElementById("box");
var p = document.getElementsByTagName("p")[0];
// 节点类型判断
console.log(box.nodeType); // 1(元素节点)
console.log(box.firstChild.nodeType); // 3(文本节点“Hello”)
// 节点关系遍历
console.log(box.parentNode); // body(box的父节点)
console.log(box.children); // [p](box的元素子节点)
console.log(p.previousSibling); // 文本节点“Hello”(p的前兄弟节点)
console.log(p.nextSibling); // null(p无后兄弟节点)
4. 元素节点关系
为了更方便地操作元素,DOM还提供了一系列只关注元素节点的属性:
| 属性 |
含义 |
语法示例 |
parentElement |
获取父元素节点 |
p.parentElement → div |
firstElementChild / lastElementChild |
获取第一个/最后一个元素子节点 |
div.firstElementChild → p |
previousElementSibling / nextElementSibling |
获取上一个/下一个兄弟元素节点 |
span.previousElementSibling → i |
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<p>段落</p>
<i>倾斜文本</i>
<span>普通文本</span>
</div>
<script>
// 父标签
var div = document.querySelector(‘div’)
var p = document.querySelector(‘p’)
var i = document.querySelector(‘i’)
var span = document.querySelector(‘span’)
// 父标签
var parent = p.parentElement // div
// 第一个子标签
var firstChildTag = div.firstElementChild // p
// 最后一个子标签
var lastChildTag = div.lastElementChild // span
// 上一个兄弟标签
var prev = span.previousElementSibling // i
// 下一个兄弟标签
var next = span.nextElementSibling // span
</script>
</body>
</html>
三、HTML基本结构操作
这部分操作主要针对整个文档的顶层结构。
// 1. 获取/修改文档标题
console.log(document.title); // 获取标题
document.title = “JS学习第10天:DOM操作”; // 修改标题
// 2. 获取body和html元素
var body = document.body; // 获取body元素
var html = document.documentElement; // 获取html元素
// 3. 获取文档编码格式
console.log(document.charset); // 输出文档编码(如UTF-8)
// 4. 获取文档URL和域名
console.log(document.URL); // 完整URL
console.log(document.domain); // 域名(如www.xxx.com)
// 5. 写入文档内容(慎用,会覆盖原有内容)
// document.write(“这是新内容“); // 覆盖整个页面内容
// 动态追加内容(在页面加载完成后使用)
window.onload = function() {
document.write(“<h3>页面加载完成后追加的内容</h3>“);
};
注意:document.write() 在页面加载完成后使用会覆盖整个文档,现代开发中应尽量避免,优先使用接下来的节点操作方法。
四、DOM节点获取(核心重点)
在操作元素之前,必须先获取它。以下是几种核心方法:
| 方法 |
含义 |
返回值 |
适用场景 |
getElementById() |
通过 id 获取 |
单个元素节点或 null |
元素有唯一 id 时(最快) |
getElementsByClassName() |
通过 class 获取 |
HTMLCollection 集合 |
多个元素共用同一 class |
getElementsByTagName() |
通过标签名获取 |
HTMLCollection 集合 |
获取同类型标签(如所有 <p>) |
querySelector() |
通过CSS选择器获取 |
匹配的第一个元素节点 |
复杂选择器场景(如 #box .child) |
querySelectorAll() |
通过CSS选择器获取 |
NodeList 集合(静态) |
获取所有匹配CSS选择器的元素 |
案例演示:
// 假设页面结构:
// <div id=“box” class=“container”>
// <p class=“text”>段落1</p>
// <p class=“text”>段落2</p>
// <span id=“span1”>文本</span>
// </div>
// 1. getElementById()
var box = document.getElementById(“box”);
var span1 = document.getElementById(“span1”);
// 2. getElementsByClassName()
var textList = document.getElementsByClassName(“text”);
console.log(textList[0]); // 第一个class为text的元素
// 遍历集合
for (var i = 0; i < textList.length; i++) {
console.log(textList[i].innerText);
}
// 3. getElementsByTagName()
var pList = document.getElementsByTagName(“p“);
console.log(pList.length); // 输出2(所有p标签)
// 4. querySelector()
var firstText = document.querySelector(“.text”); // 第一个.text元素
var boxChild = document.querySelector(“#box p”); // #box下的第一个p标签
// 5. querySelectorAll()
var allText = document.querySelectorAll(“.text”); // 所有.text元素
var allP = document.querySelectorAll(“#box p”); // #box下的所有p标签
// 遍历NodeList集合
for (var j = 0; j < allText.length; j++) {
console.log(allText[j]);
}
注意:HTMLCollection 和 NodeList 都是“类数组”,传统遍历需用 for 循环。querySelectorAll 返回的是静态集合,后续DOM变化不会更新它。
五、DOM节点操作(增删改查)
获取节点后,我们就可以对它们进行动态的增、删、改、查了。
1. 节点创建
// 1. 创建元素节点
var newDiv = document.createElement(“div“); // 创建div标签
// 2. 创建文本节点
var newText = document.createTextNode(“这是新创建的文本”);
// 3. 将文本节点添加到元素节点中
newDiv.appendChild(newText);
// 4. 设置元素属性
newDiv.id = “newDiv”;
newDiv.className = “new-class”;
2. 节点追加与插入
// 基于上面创建的newDiv
var box = document.getElementById(“box”);
// 1. 追加到目标节点末尾(appendChild)
box.appendChild(newDiv);
// 2. 插入到指定节点之前(insertBefore)
var p = document.querySelector(“.text”);
// 语法:父节点.insertBefore(新节点, 参考节点)
box.insertBefore(newDiv, p); // 将newDiv插入到p标签之前
// 3. 克隆节点(cloneNode)
var cloneDiv = newDiv.cloneNode(true); // true:克隆所有子节点;false:仅克隆自身
box.appendChild(cloneDiv); // 追加克隆节点
3. 节点修改与替换
var box = document.getElementById(“box”);
var oldP = document.querySelector(“.text”);
// 1. 创建新节点(用于替换)
var newP = document.createElement(“p“);
newP.innerText = “这是替换后的段落”;
// 2. 替换节点(replaceChild)
// 语法:父节点.replaceChild(新节点, 旧节点)
box.replaceChild(newP, oldP); // 用newP替换oldP
4. 节点删除
var box = document.getElementById(“box”);
var delP = document.querySelector(“.text”);
// 1. 通过父节点删除子节点
box.removeChild(delP);
// 2. 简化删除(先找父节点再删除)
delP.parentNode.removeChild(delP);
// 3. 清空节点所有子元素(两种方式)
// 方式1:逐个子节点删除
while (box.firstChild) {
box.removeChild(box.firstChild);
}
// 方式2:直接清空innerHTML(简单高效)
box.innerHTML = “”;
六、节点内容操作
修改元素的内容是DOM操作中最常见的需求之一。
| 属性 |
含义 |
特点 |
适用场景 |
innerText |
设置/获取元素的文本内容 |
忽略HTML标签,只处理纯文本 |
安全地插入或读取纯文本内容 |
innerHTML |
设置/获取元素的HTML内容 |
会解析HTML标签 |
需要动态插入带结构的HTML时 |
value |
设置/获取表单元素的值 |
仅适用于表单元素 |
处理输入框、文本域、下拉框等 |
案例演示:
// 假设页面有:
// <div id=“box”>原始内容</div>
// <input type=“text” id=“input1” value=“默认值”>
// <textarea id=“textarea1”>默认文本域内容</textarea>
var box = document.getElementById(“box”);
var input1 = document.getElementById(“input1”);
var textarea1 = document.getElementById(“textarea1”);
// 1. innerText用法
console.log(box.innerText); // 获取文本内容
box.innerText = “新的文本内容<strong>加粗</strong>“; // 标签会被当作文本显示
// 2. innerHTML用法
console.log(box.innerHTML); // 获取HTML内容
box.innerHTML = “新的<strong>加粗内容</strong><p>段落</p>“; // 解析HTML标签
// 3. value用法(表单元素)
console.log(input1.value); // 获取输入框值
input1.value = “修改后的输入框值”; // 设置输入框值
console.log(textarea1.value); // 获取文本域值
textarea1.value = “修改后的文本域内容”; // 设置文本域值
// 4. 追加内容(innerHTML)
box.innerHTML += “<span>追加的内容</span>“; // 不会覆盖原有内容
安全警告:innerHTML 如果直接用于插入未经验证的用户输入,可能导致XSS(跨站脚本)攻击。处理用户数据时,应优先使用 innerText 或对输入进行严格过滤。
七、节点样式操作
1. 行内样式操作(style属性)
直接操作元素的 style 对象,设置的是行内样式,优先级最高。
var box = document.getElementById(“box”);
// 1. 设置行内样式(单个样式)
box.style.width = “200px”;
box.style.height = “200px”;
box.style.backgroundColor = “red”; // 注意驼峰命名(对应CSS的background-color)
// 2. 设置多个样式
box.style.cssText = “width: 300px; height: 300px; background-color: blue; color: #fff;“;
// 3. 获取行内样式值
console.log(box.style.width); // 输出200px
console.log(box.style.backgroundColor); // 输出red
// 4. 移除行内样式
box.style.removeProperty(“background-color”); // 移除指定样式
box.style.cssText = “”; // 清空所有行内样式
2. 计算样式获取(getComputedStyle)
获取元素最终应用的所有样式(包括内联、内部、外部样式表),此属性为只读。
var box = document.getElementById(“box”);
// 获取计算样式(非IE兼容,简洁版)
function getStyle(element, attr) {
return window.getComputedStyle(element, null)[attr];
}
// 用法
console.log(getStyle(box, “width”)); // 输出最终宽度(如200px)
console.log(getStyle(box, “backgroundColor”)); // 输出最终背景色
console.log(getStyle(box, “fontSize”)); // 输出最终字体大小
八、节点类名操作
通过增减CSS类来改变样式,比直接操作 style 更灵活、更易维护。
// 假设页面有 <div id=“box” class=“container”></div>
// CSS样式:.active { background: red; } .hide { display: none; }
var box = document.getElementById(“box”);
// 1. 传统className操作
console.log(box.className); // 输出“container”
box.className = “active”; // 覆盖类名
box.className += “ active”; // 追加类名(注意前面加空格)
// 2. 现代浏览器推荐:classList方法(简洁高效)
box.classList.add(“active”); // 追加类名
box.classList.remove(“active”); // 移除类名
box.classList.toggle(“hide”); // 切换类名(存在则删,无则加)
box.classList.contains(“active”); // 判断是否包含类名(返回布尔值)
box.classList.replace(“container”, “new-container”); // 替换类名
九、节点属性操作
操作HTML标签上的各种属性。
// 假设页面有 <a id=“link” href=“https://www.baidu.com” title=“百度”>百度</a>
// <img id=“img1” src=“default.jpg” alt=“默认图片”>
var link = document.getElementById(“link”);
var img1 = document.getElementById(“img1”);
// 1. 获取属性(getAttribute)
console.log(link.getAttribute(“href”)); // 输出https://www.baidu.com
console.log(img1.getAttribute(“alt”)); // 输出默认图片
// 2. 设置属性(setAttribute)
link.setAttribute(“href”, “https://www.jd.com”); // 修改href属性
link.setAttribute(“title”, “京东”); // 修改title属性
img1.setAttribute(“src”, “new.jpg”); // 修改图片路径
// 3. 移除属性(removeAttribute)
link.removeAttribute(“title”); // 移除title属性
img1.removeAttribute(“alt”); // 移除alt属性
// 4. 直接操作内置属性(简化写法)
console.log(link.href); // 输出完整URL(含域名)
link.href = “https://www.taobao.com”; // 直接修改href
img1.src = “other.jpg”; // 直接修改src
// 5. 表单属性操作(disabled、checked等)
var btn = document.getElementById(“btn”);
btn.disabled = true; // 禁用按钮
btn.disabled = false; // 启用按钮
var checkbox = document.getElementById(“checkbox1”);
console.log(checkbox.checked); // 判断是否选中
checkbox.checked = true; // 设置选中
十、节点尺寸与位置操作
1. 节点尺寸
var box = document.getElementById(“box”);
// 1. 获取计算后尺寸(含padding、border,不含margin)
var width = box.offsetWidth; // 元素总宽度
var height = box.offsetHeight; // 元素总高度
console.log(“总宽度:“ + width + “px,总高度:“ + height + “px”);
// 2. 获取内容区尺寸(不含padding、border)
var clientWidth = box.clientWidth; // 内容区宽度
var clientHeight = box.clientHeight; // 内容区高度
console.log(“内容区宽度:“ + clientWidth + “px”);
// 3. 获取滚动内容尺寸(元素内部滚动区域总尺寸)
var scrollWidth = box.scrollWidth; // 滚动内容总宽度
var scrollHeight = box.scrollHeight; // 滚动内容总高度
console.log(“滚动内容总高度:“ + scrollHeight + “px”);
2. 节点位置
var box = document.getElementById(“box”);
// 1. 相对于视口的位置(不含滚动距离)
var clientTop = box.clientTop; // 上边框宽度
var clientLeft = box.clientLeft; // 左边框宽度
console.log(“上边框宽度:“ + clientTop + “px”);
// 2. 相对于页面的位置(含滚动距离)
var offsetTop = box.offsetTop; // 距离页面顶部的距离
var offsetLeft = box.offsetLeft; // 距离页面左侧的距离
console.log(“距离顶部:“ + offsetTop + “px,距离左侧:“ + offsetLeft + “px”);
// 3. 元素滚动距离(自身内部滚动)
var scrollTop = box.scrollTop; // 垂直滚动距离
var scrollLeft = box.scrollLeft; // 水平滚动距离
// 设置滚动距离
box.scrollTop = 100; // 垂直滚动到100px位置
3. 页面卷去尺寸(滚动距离)
常用于实现“回到顶部”、“滚动加载”等功能。
// 获取页面滚动距离(兼容写法)
function getScrollOffset() {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
return { top: scrollTop, left: scrollLeft };
}
// 用法:监听滚动事件
window.onscroll = function() {
var scroll = getScrollOffset();
console.log(“页面滚动距离顶部:“ + scroll.top + “px”);
// 示例:滚动距离超过500px显示回到顶部按钮
var topBtn = document.getElementById(“topBtn”);
topBtn.style.display = scroll.top > 500 ? “block” : “none”;
};
// 回到顶部功能(平滑效果)
function scrollToTop() {
var timer = setInterval(function() {
var scroll = getScrollOffset();
if (scroll.top <= 0) return clearInterval(timer);
window.scrollTo(0, scroll.top - 50);
}, 16);
}
十一、DOM操作注意事项
- 操作时机:确保DOM元素已加载。可将脚本放在页面底部,或使用
window.onload / DOMContentLoaded 事件。
- 性能优化:频繁的DOM操作(尤其是引起布局变化的操作)会触发重排与重绘,影响性能。可以考虑使用
DocumentFragment 进行批量操作,或对样式进行集中修改。
- 兼容性:本文介绍的方法均为现代标准API,主流浏览器支持良好。
querySelector 系列是当前最推荐的选择。
- 遍历集合:
HTMLCollection 和 NodeList 是“活的”或“静态的”类数组,遍历时需注意方法。现代环境中也可用 Array.from() 转换后使用数组方法。
- 安全第一:牢记
innerHTML 的XSS风险,绝不信任并直接插入用户的输入。
十二、总结与学习建议
DOM操作是前端开发者必须掌握的技能。本文系统地介绍了从DOM树概念到节点增删改查、样式属性调整、尺寸位置计算的全流程。每个知识点都配有可运行的代码示例,建议你务必在浏览器控制台中动手实践。
学习建议:
- 实践为王:复制文中的案例代码,在简单的HTML页面中运行,并通过控制台观察结果。
- 重点突破:核心是“获取节点”(
querySelector, getElementById)和“操作节点”(内容、样式、属性)。反复练习直至熟练。
- 项目驱动:尝试用纯DOM API实现一些小组件,如动态待办事项列表、图片切换器、简易计算器。这能极大巩固你的综合应用能力。
- 理解原理:区分不同方法和属性的适用场景(如
innerText vs innerHTML, offsetHeight vs clientHeight),理解其背后的差异。
掌握DOM是您构建交互式网页的第一步。接下来,您需要学习事件处理,为这些静态的元素注入“灵魂”,响应用户的点击、输入等操作,那时网页才真正变得生动起来。在云栈社区,你可以找到更多关于JavaScript及前端技术的深度讨论和实战资源,与众多开发者一起交流成长。