JavaScript进阶篇:DOM核心知识解读
目录
1. 什么是DOM?
2. DOM树结构解析
3. 常用DOM节点类型
4. 选取DOM元素的方法
5. 操作DOM节点的常用API
6. 事件处理与事件委托
7. DOM操作性能优化建议
8. 补充说明
8.1 .innerHTML/.textContent/.innerText如何选择
8.1.1 .innerHTML
8.1.2 .textContent
8.1.3 .innerText
8.1.4 推荐使用建议
8.2 如何使用 DocumentFragment 来进行性能优化(DOM优化)
1. 什么是DOM?
DOM(Document Object Model)是HTML和XML文档的编程接口,是浏览器将网页解析成的一棵树形结构。它将网页中的每个元素、属性、文本节点都表示为一个节点对象,JavaScript通过操作这些节点对象,实现对网页内容的动态操作。
2. DOM树结构解析
浏览器解析HTML生成DOM树,节点之间存在层级关系:
- 根节点:
document
对象,代表整个HTML文档。 - 元素节点:对应HTML标签,如
<div>
,<p>
。 - 文本节点:元素中的文本内容。
- 属性节点:元素的属性,如
id
,class
。
DOM树是一个多叉树结构,节点之间通过父子、兄弟关系相连。
例如:
<html><body><div id="app"><p>Hello</p></div></body>
</html>
Document└── html└── body└── div#app└── p└── "Hello"
3. 常用DOM节点类型
节点类型 | 说明 | 示例 |
---|---|---|
Element节点 | HTML元素节点 | <div> , <span> |
Text节点 | 元素内的文本内容 | 元素中的纯文本 |
Attribute节点 | 元素的属性 | id="header" |
Comment节点 | 注释节点 | <!-- 注释 --> |
4. 选取DOM元素的方法
document.getElementById(id)
:通过ID获取元素。document.getElementsByClassName(className)
:通过类名获取元素集合。document.getElementsByTagName(tagName)
:通过标签名获取元素集合。document.querySelector(selector)
:通过CSS选择器获取第一个匹配元素。document.querySelectorAll(selector)
:通过CSS选择器获取所有匹配元素。
示例:
const header = document.getElementById('header');
const items = document.getElementsByClassName('item');
const firstButton = document.querySelector('button');
const allButtons = document.querySelectorAll('button');
5. 操作DOM节点的常用API
(1)获取和修改内容
.innerHTML
:获取或设置元素的HTML内容。.textContent
:获取或设置元素的文本内容。.innerText
:获取或设置元素的“渲染”文本(兼容性稍差)。
(2)操作属性和样式
.getAttribute(name)
/.setAttribute(name, value)
:获取和设置属性。.removeAttribute(name)
:移除属性。.classList
:操作元素的class,常用方法有add()
,remove()
,toggle()
,contains()
。.style
:直接操作元素的内联样式。
(3)添加、删除和替换节点
document.createElement(tagName)
:创建元素节点。parentNode.appendChild(childNode)
:添加子节点。parentNode.insertBefore(newNode, referenceNode)
:插入节点。parentNode.removeChild(childNode)
:删除节点。parentNode.replaceChild(newNode, oldNode)
:替换节点。
6. 事件处理与事件委托
(1)事件绑定
element.onclick = function() {}
:传统绑定,后面绑定的事件会覆盖前面绑定的事件。element.addEventListener(event, handler, useCapture)
:推荐方式,同一个事件可绑定多个事件处理器。
(2)事件对象
事件处理函数可以接收事件对象,包含事件相关信息。
element.addEventListener('click', function(event) {console.log('点击位置:', event.clientX, event.clientY);
});
(3)事件委托
事件委托依赖于“事件冒泡”机制,即事件从目标元素向上逐层传递,直到 document
根节点。通过事件冒泡机制,将事件绑定在父元素上,减少事件绑定数量,提高性能。
document.getElementById('list').addEventListener('click', function(event) {if(event.target && event.target.nodeName === 'LI') {console.log('点击了列表项:', event.target.textContent);}
});
7. DOM操作性能优化建议
- 尽量减少频繁的DOM操作,批量修改时使用文档碎片(DocumentFragment)。
- 使用
classList
操作样式,避免直接操作style
属性带来的重排。 - 事件委托减少事件绑定,提高性能。
- 避免使用
innerHTML
频繁重写整个内容,优先使用节点操作。
8. 补充说明
8.1 .innerHTML/.textContent/.innerText如何选择
8.1.1 .innerHTML
- 作用:获取或设置元素内部的HTML代码(包括标签)。
- 返回值类型:字符串,包含元素内的所有HTML标记和文本。
- 特点:
- 设置时,会解析字符串中的HTML标签,渲染成对应的元素。
- 读取时,返回的是元素内部的完整HTML结构字符串。
- 性能:操作较重,设置时会重新解析HTML,可能导致页面重绘和回流。
适用场景:
- 需要动态生成复杂的HTML结构时,比如插入多层嵌套的标签。
- 需要获取元素内部的完整HTML代码。
示例:
const div = document.querySelector('#example');
div.innerHTML = '<p><strong>加粗文本</strong>和普通文本</p>';
8.1.2 .textContent
- 作用:获取或设置元素内部的纯文本内容(不包含任何HTML标签)。
- 返回值类型:字符串,只包含文本内容。
- 特点:
- 读取时,返回元素内所有文本,包括隐藏元素的文本。
- 设置时,会将内容作为纯文本插入,不会解析HTML标签。
- 性能较好,操作快速。
- 兼容性:各主流浏览器支持良好。
适用场景:
- 只需要获取或设置纯文本内容,不需要处理HTML标签。
- 需要避免XSS攻击,防止用户输入被当作HTML解析。
- 性能要求较高时优先使用。
示例:
const div = document.querySelector('#example');
div.textContent = '<p>这只是普通文本,不会被解析成HTML</p>';
8.1.3 .innerText
- 作用:获取或设置元素“渲染”后的文本内容,类似于用户实际看到的文本。
- 返回值类型:字符串,包含可见文本。
- 特点:
- 读取时,会忽略隐藏元素(如
display:none
的元素)中的文本。 - 读取结果会考虑CSS样式和换行符,返回格式更接近页面显示效果。
- 设置时,会以纯文本形式插入,不解析HTML标签。
- 兼容性较早期IE浏览器好,但标准化程度不如
textContent
。 - 性能比
textContent
稍差,因为需要计算样式和布局。
- 读取时,会忽略隐藏元素(如
适用场景:
- 需要获取用户实际看到的文本内容(排除隐藏元素)。
- 需要获取带有换行和格式的文本。
- 需要操作显示文本,而非所有文本。
示例:
const div = document.querySelector('#example');
console.log(div.innerText); // 只输出可见文本
8.1.4 推荐使用建议
- 需要插入或获取HTML结构时,用
.innerHTML
。 - 只需要纯文本时,优先用
.textContent
,性能更好且安全。 - 需要获取页面上用户实际看到的文本(排除隐藏)且考虑格式时,用
.innerText
。
8.2 如何使用 DocumentFragment 来进行性能优化(DOM优化)
(1)使用了 DocumentFragment
const fragment = document.createDocumentFragment();for (let i = 0; i < 1000; i++) {const li = document.createElement('li');li.textContent = `Item ${i}`;fragment.appendChild(li);
}document.getElementById('list').appendChild(fragment);
代码的逻辑是:
-
创建一个空的
DocumentFragment
。 -
在循环中生成 1000 个
<li>
元素,并全部添加到这个fragment
中。 -
最后一次性将
fragment
插入到真实 DOM(#list
)中。
(2)不使用 DocumentFragment
const list = document.getElementById('list');for (let i = 0; i < 1000; i++) {const li = document.createElement('li');li.textContent = `Item ${i}`;list.appendChild(li); // 每次都操作真实 DOM
}
这样写的问题有:
-
每次
appendChild()
都会修改 DOM 树; -
浏览器每次都要重新计算布局、重绘(Reflow + Repaint);
-
重复 1000 次,就触发了 1000 次性能消耗极高的渲染流程。
(3)DocumentFragment 如何优化性能
DocumentFragment
是一个存在于内存中的轻量级 DOM 容器。
它不属于页面的真实 DOM 树,因此:
-
向
fragment
添加元素 不会触发浏览器重绘或重排; -
当你最后将整个
fragment
插入到真实 DOM 时,浏览器只会触发 一次 重绘与重排。
也就是说:从 1000 次 DOM 操作 → 优化为 1 次 DOM 操作。
(4)总结:
DocumentFragment
是一个“离线 DOM 容器”,- 子节点插入真实 DOM 时会“转移”过去,而 fragment 本身不会出现在 DOM 树中,
- 因此实现了批量插入、减少重绘重排,从而优化性能。