Javascript Dom操作
1.DOM 节点类型
DOM(文档对象模型)将 HTML 文档表示为一棵树,其中每个组成部分都是一个节点(Node)。常见的节点类型包括:
- 元素节点(Element Node):如div、p 等标签。
- 属性节点(Attribute Node):如 id="box"、title="..." 等(注意:在现代 DOM 标准中,属性通常不被视为独立的“子节点”,但可通过 attributes 访问)。
- 文本节点(Text Node):元素内的文字内容,例如 "我是谁"。
⚠️ 补充说明:虽然“属性节点”在早期 DOM 规范中被定义为一种节点类型(nodeType === 2),但在当前 Web 开发实践中,属性一般通过元素的 getAttribute/setAttribute 等方法操作,而非直接操作属性节点。不过 getAttributeNode() 和 element.attributes 仍可访问它们。
节点的常用属性
| 属性 | 说明 |
|---|---|
| nodeName | 节点的名称(如元素名、#text、#comment 等) |
| nodeType | 节点的类型(以数字常量表示) |
| nodeValue | 节点的值(对元素节点恒为 null,对文本节点为文本内容) |
nodeType 的常见取值
<script>
//依次输出Node对象的属性和值
for(var key in Node){
console.log(key,Node[key])
//只列出常用的
//ELEMENT_NODE 1 (元素节点)
//ATTRIBUTE_NODE 2 (属性节点)
//TEXT_NODE 3 (文本性节点)
//COMMENT_NODE 8 (注释节点)
//DOCUMENT_NODE 9 (文档节点,即document)
//DOCUMENT_TYPE_NODE 10 (文档类型节点,即<!DOCTYPE html>)
//DOCUMENT_FRAGMENT_NODE 11 (文档片断节点,DocumentFragment)
}
</script>
各个节点属性值示例:
<body>
<div id="box" title="我在哪">我是谁</div>
</body>
<script>
// 获取元素节点
var box = document.querySelector('#box')
// 元素节点的属性
console.log(box.nodeName);//DIV
console.log(box.nodeType); //1
console.log(box.nodeValue); //一律为null
// 根据属性名获取单个属性节点
var attrTitle = box.getAttributeNode("title")
// 属性节点的属性
console.log(attrTitle.nodeName);//title
console.log(attrTitle.nodeType); //2
console.log(attrTitle.nodeValue); //"我在哪"
//获取所有的属性节点
var atttibues = box.attributes
//获取特定的节点 下标和属性名都可以获取
var idNode = atttibues.id //atttibues[0] id="box"
console.log(idNode.nodeName,idNode.nodeType,idNode.nodeValue)//id 2 box
// 获取box的文本节点(没有专门的方法用来获取文本节点,但是这个div的第一个子节点就是文本节点)
var textNode = box.childNodes[0];
// 文本节点的属性
console.log(textNode.nodeName);//#text
console.log(textNode.nodeType); //3
console.log(textNode.nodeValue); //"我是谁"
</script>
✅ 关键提示:
- 元素节点的 nodeValue 始终为 null;
- 文本节点的内容存储在 nodeValue(或等效的 data 属性)中;
- 属性节点虽存在,但日常开发中更推荐使用 element.getAttribute('attr') 而非操作属性节点本身。
2.Dom获取
通过选择器拿到的元素列表有2类:NodeList和HTMLCollection,区别如下:
| 特性 | NodeList | HTMLCollection |
|---|---|---|
| 包含的节点类型 | 任意 Node(包括元素、文本、注释等) | 仅 HTML 元素节点(Element) |
| 是否动态(live) | 通常静态(但有例外) | 总是动态(live) |
| 是否有数组方法(如 map, forEach) | 有(现代浏览器支持 forEach 等) | ❌ 没有(不是可迭代对象,除非手动转) |
| 能否用 for...of 遍历 | ✅ 可以(现代环境) | ✅ 可以(现代浏览器也实现了 Symbol.iterator) |
| 典型获取方式 | querySelectorAll, childNodes | getElementsBy* 系列 |
HTMLCollection
- 只包含 Element 节点(比如 div, p),自动过滤掉文本、注释等。
- 是“动态集合”(live collection):
- 如果 DOM 发生变化(比如新增/删除匹配的元素),HTMLCollection 会自动更新。
- 没有数组原型方法(如 map, filter),但可以用 Array.from() 或展开运算符转换。
NodeList
- 可以包含任何类型的 Node(元素、文本节点、注释等)。
- 分为两种:
- 静态 NodeList:如 querySelectorAll() 返回的 —— 不会随 DOM 变化而更新。
- 动态 NodeList:如 childNodes 返回的 —— 会随 DOM 更新(这是少数例外!)。
静态 vs 动态 示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<style>
*{padding: 0;margin: 0;}
</style>
<body>
<ul>
<li class="test">1</li>
<li class="test">2</li>
<li class="test">3</li>
<li class="test">4</li>
</ul>
</body>
</html>
<script>
var lis = document.getElementsByTagName('li')
var liByQuery = document.querySelectorAll('li')
console.log(lis.length) // 4
console.log(liByQuery.length) // 4
// 动态添加li标签
document.querySelector('ul').innerHTML += "<li class='test'>5</li>"
console.log(lis.length) // 5
console.log(liByQuery.length) // 4
</script>
遍历:NodeList,可以使用foreach()遍历,而HTMLCollection没有forEach方法,只能使用for循环遍历
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=2.0">
<title>Test</title>
</head>
<body>
<ul>
<li class="test">1</li>
<li class="test">2</li>
<li class="test">3</li>
<li class="test">4</li>
</ul>
</body>
</html>
<script>
var li = document.getElementsByTagName('li')
var li2 = document.getElementsByClassName('test')
var li3 = document.querySelectorAll('li')
var li4 = document.querySelector('ul').children
console.log(li) // HTMLCollection(4) [li.test, li.test, li.test, li.test]
console.log(li2) // HTMLCollection(4) [li.test, li.test, li.test, li.test]
console.log(li3) // NodeList(4) [li.test, li.test, li.test, li.test]
console.log(li4) // HTMLCollection(4) [li.test, li.test, li.test, li.test]
</script>
3.子元素 vs 子节点
| 属性 | 说明 | 返回类型 |
|---|---|---|
| children | 获取当前 DOM 元素的所有子元素(仅元素节点) | HTMLCollection |
| firstElementChild | 获取第一个子元素(等价于 children[0]) | Element |
| lastElementChild | 获取最后一个子元素(等价于 children[children.length - 1]) | Element |
| childElementCount | 返回子元素的数量(等价于 children.length) | number |
| childNodes | 获取当前 DOM 元素的所有子节点(包括元素节点、文本节点、注释节点等,不包含属性节点) | NodeList |
| firstChild | 获取第一个子节点(等价于 childNodes[0]) | Node |
| lastChild | 获取最后一个子节点(等价于 childNodes[childNodes.length - 1]) | Node |
💡 关键区别:
- “子元素”仅指 标签 这类 HTML 元素节点;
- “子节点”包含所有类型的节点,例如换行、空格产生的文本节点(#text)。
示例代码:
<body>
<div id="app">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
</body>
<script>
var ul = document.querySelector('ul')
//子元素
console.log(ul.children) //HTMLCollection(4) [li, li, li, li]
//子元素数量
console.log(ul.childElementCount) //4
console.log(ul.children.length) //4
//第一个子元素(等价)
console.log(ul.children[0] === ul.firstElementChild) //true
//最后一个子元素(等价)
console.log(ul.children[3] === ul.lastElementChild) //true
//子节点(把代码回车换行当作文本节点也算进去了)
console.log(ul.childNodes) //NodeList(9) [text, li, text, li, text, li, text, li, text]
//第一个子节点
console.log(ul.firstChild) //#text
//等价
console.log(ul.childNodes[0] === ul.firstChild) //true
console.log(ul.childNodes[8] === ul.lastChild) //true
</script>
📌 注意:
在 HTML 源码中,标签之间的换行符和空格会被解析为文本节点(Text Node),因此 childNodes 的长度通常比 children 大。若想避免干扰,可使用压缩后的 HTML(无多余空白),或始终优先使用 children / firstElementChild 等“仅元素”接口。
4.父元素与父节点
| 属性 / 方法 | 说明 | 调用者 | 备注 |
|---|---|---|---|
| parentElement | 获取当前 DOM 元素的父元素节点(仅限 Element 类型) | 任意 Element | 若父节点不是元素(如文档片段、文档根等),返回 null |
| parentNode | 获取当前 DOM 节点的父节点(可以是任意 Node 类型) | 任意 Node | 总是返回实际的父节点(可能是 Element、Document、DocumentFragment 等) |
| closest(selector) | 从当前元素向上遍历祖先树,返回第一个匹配指定 CSS 选择器的祖先元素 | 任意 Element | 若未找到则返回 null;选择器参数不能为空 |
💡 关键区别:
- parentNode 是所有节点都有的属性,返回的是直接父节点(类型不限);
- parentElement 是 Element 接口的属性,只在父节点是元素时才返回它,否则返回 null。
- 在普通 HTML 文档中,绝大多数元素的父节点本身就是元素(如 li 的父是 ul),所以两者通常相等;
- 但在特殊场景(如操作 DocumentFragment、 的父是 document 等)下,二者结果可能不同。
示例代码:
<body>
<div id="app">
<div class="box">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
</div>
</body>
<script>
//父元素
var ul = document.querySelector('ul')
//第一个子元素
var li = document.querySelector('ul > li')
//等价
console.log(ul === li.parentElement) //true
console.log(ul === li.parentNode) //true
//最近的祖先元素
console.log(li.closest('.box')) //div.box
</script>
5.兄弟元素与兄弟节点
| 属性 | 说明 | 调用者 |
|---|---|---|
| previousElementSibling | 获取当前元素的前一个兄弟元素节点(仅限 Element 类型) | 当前 DOM 元素 |
| nextElementSibling | 获取当前元素的后一个兄弟元素节点(仅限 Element 类型) | 当前 DOM 元素 |
| previousSibling | 获取当前节点的前一个兄弟节点(可能是元素、文本、注释等) | 当前 DOM 节点 |
| nextSibling | 获取当前节点的后一个兄弟节点(可能是元素、文本、注释等) | 当前 DOM 节点 |
💡 关键区别:
- 带 Element 的属性只返回元素节点,自动跳过文本、注释等;
- 不带 Element 的属性返回所有类型的兄弟节点,包括 HTML 源码中的空白字符生成的文本节点(#text)
示例代码:
<body>
<div id="app">
<ul>
<li id="li1">1</li>
<li id="li2">2</li>
<li id="li3">3</li>
<li id="li4">4</li>
</ul>
</div>
</body>
<script>
const li1 = document.querySelector('#li1');
const li2 = document.querySelector('#li2');
const li3 = document.querySelector('#li3');
const li4 = document.querySelector('#li4');
// —— 兄弟元素(仅元素节点) ——
console.log(li2.previousElementSibling === li1); // true
console.log(li2.nextElementSibling === li3); // true
// —— 兄弟节点(包含空白文本节点) ——
console.log(li2.previousSibling); // #text(位于 <li1> 和 <li2> 之间的换行+缩进)
console.log(li2.nextSibling); // #text(位于 <li2> 和 <li3> 之间的换行+缩进)
// 验证节点类型
console.log(li2.previousSibling.nodeType === Node.TEXT_NODE); // true
console.log(li2.previousSibling.nodeValue.trim() === ''); // true(内容为空白)
</script>
6.添加元素
DOM 提供了多种方式向文档中插入新节点,主要分为两类:作为子元素插入 和 作为兄弟节点插入
作为子元素插入
| 方法 | 说明 |
|---|---|
| parent.appendChild(child) | 将 child 作为 parent 的最后一个子节点插入 |
| parent.insertBefore(newNode, referenceNode) | 将 newNode 插入到 parent 中的 referenceNode 之前 |
示例代码
<body>
<ul>
<li>1</li>
<li id="test">2</li>
<li>3</li>
</ul>
</body>
</html>
<script>
const ul = document.querySelector('ul');
// 创建新 <li> 并追加到末尾
const li4 = document.createElement('li');
li4.textContent = '4'; // 推荐使用 textContent 而非 innerHTML(防 XSS)
ul.appendChild(li4);
// 创建另一个 <li> 并插入到 #test 之前
const liLife = document.createElement('li');
liLife.textContent = 'life';
const liTest = document.querySelector('#test');
ul.insertBefore(liLife, liTest);
</script>
运行结果
<ul>
<li>1</li>
<li>life</li>
<li id="test">2</li>
<li>3</li>
<li>4</li>
</ul>
作为兄弟节点插入(现代 API)
| 方法 | 说明 |
|---|---|
| node.before(...nodes) | 在 node 之前插入一个或多个节点(可为元素、文本等) |
| node.after(...nodes) | 在 node 之后插入一个或多个节点 |
⚠️ IE 不支持,但现代浏览器(Chrome、Firefox、Safari、Edge)均已支持
示例代码
<body>
<div id="app">
<ul>
<li id="li1">1</li>
<li id="li2">2</li>
<li id="li3">3</li>
</ul>
</div>
</body>
<script>
// 在 #li1 前插入新 <li>
const li0 = document.createElement('li');
li0.id = 'li0';
li0.textContent = '0';
document.querySelector('#li1').before(li0);
// 在 #li3 后插入新 <li>
const li4 = document.createElement('li');
li4.id = 'li4';
li4.textContent = '4';
document.querySelector('#li3').after(li4);
// 也可以直接插入文本(虽然较少用)
// li0.before('⚠️ '); // 会在 li0 前插入一个文本节点
</script>
运行结果
<ul>
<li id="li0">0</li>
<li id="li1">1</li>
<li id="li2">2</li>
<li id="li3">3</li>
<li id="li4">4</li>
</ul>
7.移除与替换元素
DOM 提供了多种方式来移除或替换节点,分为两类操作:通过父节点操作子节点(传统方式)和节点自我操作(现代方式)
传统方式(通过父节点)
| 方法 | 说明 |
|---|---|
| parent.removeChild(child) | 从 parent 中移除指定的子节点 child |
| parent.replaceChild(newNode, oldNode) | 用 newNode 替换 parent 中的 oldNode |
✅ 这些方法兼容性极好,适用于所有浏览器(包括 IE)
现代方式(节点自我操作)
| 方法 | 说明 |
|---|---|
| node.remove() | 移除自身(无需通过父节点) |
| node.replaceWith(...nodes) | 用一个或多个新节点替换自身 |
IE 不支持,但在 Chrome、Firefox、Safari、Edge 等现代浏览器中已广泛支持
示例代码
<body>
<div id="app">
<ul>
<li id="li1">1</li>
<li id="li2">2</li>
<li id="li3">3</li>
</ul>
</div>
</body>
<script>
const ul = document.querySelector('ul');
const li1 = document.querySelector('#li1');
const li2 = document.querySelector('#li2');
const li3 = document.querySelector('#li3');
// —— 移除元素 ——
li1.remove(); // 方式1:自我移除(现代)
ul.removeChild(li2); // 方式2:通过父节点移除(传统)
// —— 替换元素 ——
const newLi = document.createElement('li');
newLi.textContent = 'replaced li'; // 推荐使用 textContent 防 XSS
ul.replaceChild(newLi, li3); // 方式1:通过父节点替换(传统)
// 或者(如果 li3 还存在):
// li3.replaceWith(newLi); // 方式2:自我替换(现代)
</script>
运行结果:(第一,第二个被移除,第三个被替换)
<ul>
<li>replaced li</li>
</ul>
8.复制 DOM 元素
使用 element.cloneNode() 可复制一个 DOM 节点:
| 调用方式 | 说明 |
|---|---|
| cloneNode(true) | 深拷贝:复制元素本身、属性及其所有后代节点(包括子元素和文本) |
| cloneNode(false) 或 cloneNode() | 浅拷贝:仅复制元素本身和属性,不包含任何子节点(如文本、子标签等) |
坑爹的W3C不解释,查文档只推荐MDN
示例代码
<body>
<div id="app">
<ul>
<li class="text-align">hello world</li>
</ul>
</div>
</body>
<script>
const ul = document.querySelector('ul');
const li = document.querySelector('li');
// 两次浅拷贝(不带子节点)
ul.appendChild(li.cloneNode()); // 等价于 cloneNode(false)
ul.appendChild(li.cloneNode(false));
// 一次深拷贝(带子节点,包括文本)
ul.appendChild(li.cloneNode(true));
</script>
运行结果:(可以看出,第二第三都是浅拷贝而来,没有带上子节点)
<li class="text-align">hello world</li> <!-- 原始节点 -->
<li class="text-align"></li> <!-- 浅拷贝 1 -->
<li class="text-align"></li> <!-- 浅拷贝 2 -->
<li class="text-align">hello world</li> <!-- 深拷贝 -->
补充说明
- cloneNode() 不会复制通过 addEventListener 添加的事件监听器;
- 若元素有 id 属性,克隆后会产生重复 ID,需手动处理;
9.滚动到目标元素所在位置
有两种常用方式可让页面(或可滚动容器)自动滚动到某个元素的位置:
使用锚点链接(HTML 原生方式)
通过 a 标签的 href 属性指向目标元素的 id(格式为 #id),点击后浏览器会自动滚动到该元素
<body>
<div>
<!-- 使用a链接进行滚动 -->
<a href="#box3">滚动到3</a>
</div>
<div class="box" id="box1">1</div>
<div class="box" id="box2">2</div>
<div class="box" id="box3">3</div>
</body>
✅ 优点:无需 JavaScript,语义清晰,支持浏览器历史记录(可后退);
使用 element.scrollIntoView()(JavaScript 方式)
调用元素的 scrollIntoView() 方法,可编程控制滚动行为
基础用法
<button>滚动到 #box3</button>
<div class="box" id="box1">1</div>
<div class="box" id="box2">2</div>
<div class="box" id="box3">3</div>
<script>
const btn = document.querySelector('button');
btn.onclick = function () {
const box3 = document.querySelector('#box3');
box3.scrollIntoView(); // 默认平滑滚动到视口顶部(行为因浏览器而异)
};
</script>
高级用法(推荐)
scrollIntoView() 支持传入配置对象,精确控制滚动行为:
box3.scrollIntoView({
behavior: 'smooth', // 滚动动画:'auto'(默认,瞬间跳转)或 'smooth'(平滑滚动)
block: 'start', // 垂直对齐:'start' | 'center' | 'end' | 'nearest'
inline: 'nearest' // 水平对齐(对行内元素或横向滚动有用)
});
⚠️ 兼容性:behavior: 'smooth' 在 IE 中不支持,但现代浏览器(Chrome ≥61, Firefox ≥36, Safari ≥15.4)均已支持
10.获取与设置标签内容
DOM 提供了多种方式读取或修改元素的内容,它们的行为差异显著:
| 属性 | 说明 | 是否解析 HTML | 是否包含隐藏内容 | 是否保留格式/空白 |
|---|---|---|---|---|
| el.innerHTML | 获取/设置元素内部的 HTML 字符串 | ✅ 是(赋值时会渲染标签) | ✅ 包含注释、隐藏元素等所有子节点 | ✅ 保留原始 HTML 格式(包括换行、缩进) |
| el.outerHTML | 获取/设置元素自身及其内部的完整 HTML 字符串 | ✅ 是 | ✅ 同 innerHTML,但包含当前标签本身 | ✅ 保留完整结构 |
| el.textContent | 获取/设置元素内所有文本节点的拼接结果(纯文本) | ❌ 否(赋值时 < 不会被解析为标签) | ✅ 包含隐藏元素、注释中的文本(注释文本不可见,但 textContent 不包含注释内容!) | ✅ 保留所有空白字符(包括换行、缩进) |
| el.innerText | 获取元素内用户可见的文本内容 | ❌ 否 | ❌ 忽略 display: none、visibility: hidden 等不可见文本 | ❌ 按视觉渲染结果处理空白(类似用户复制粘贴的效果) |
代码示例
<body>
<div class="box">
<span style="display: none;">
隐藏文本
</span>
<!-- 注释 -->
<span>
123
</span>
<span>
456
</span>
<div>
另一行
</div>
</div>
</body>
<script>
var box = document.querySelector('.box')
console.log('---innerHTML---')
console.log(box.innerHTML)
console.log('---textContent---')
console.log(box.textContent)
console.log('---innerText---')
console.log(box.innerText)
</script>


浙公网安备 33010602011771号