您现在的位置是:网站首页 > DOM树结构文章详情
DOM树结构
陈川
【
JavaScript
】
1181人已围观
6707字
DOM树结构的基本概念
DOM(Document Object Model)树是HTML文档在内存中的表示形式。当浏览器加载HTML页面时,会解析HTML代码并构建一个由节点组成的树状结构。这个结构中的每个节点都对应着HTML文档中的一个元素、属性或文本内容。DOM树的核心在于它提供了程序matic访问和操作文档内容的能力。
<!DOCTYPE html>
<html>
<head>
<title>示例页面</title>
</head>
<body>
<div id="container">
<h1>标题</h1>
<p class="content">段落内容</p>
</div>
</body>
</html>
上面这个简单HTML文档对应的DOM树结构如下:
- 文档节点(Document)
- html元素节点
- head元素节点
- title元素节点
- 文本节点"示例页面"
- title元素节点
- body元素节点
- div元素节点(id="container")
- h1元素节点
- 文本节点"标题"
- p元素节点(class="content")
- 文本节点"段落内容"
- h1元素节点
- div元素节点(id="container")
- head元素节点
- html元素节点
DOM节点类型详解
DOM树由多种不同类型的节点组成,每种节点都有特定的属性和方法。最常见的节点类型包括:
- 文档节点(Document):整个文档的根节点
- 元素节点(Element):HTML标签对应的节点
- 属性节点(Attr):元素的属性
- 文本节点(Text):元素或属性中的文本内容
- 注释节点(Comment):HTML注释
// 获取节点类型示例
const element = document.getElementById('container');
console.log(element.nodeType); // 1 (Node.ELEMENT_NODE)
console.log(element.firstChild.nodeType); // 3 (Node.TEXT_NODE) 如果有空白字符
节点类型对应的常数值:
- Node.ELEMENT_NODE:1
- Node.ATTRIBUTE_NODE:2
- Node.TEXT_NODE:3
- Node.COMMENT_NODE:8
- Node.DOCUMENT_NODE:9
DOM树的遍历方法
遍历DOM树是前端开发中的常见操作,有多种方法可以实现:
父子关系遍历
const div = document.querySelector('div');
// 获取父节点
console.log(div.parentNode); // body元素
// 获取所有子节点(包括文本节点和注释节点)
console.log(div.childNodes);
// 获取第一个子节点
console.log(div.firstChild);
// 获取最后一个子节点
console.log(div.lastChild);
兄弟关系遍历
const p = document.querySelector('p');
// 获取前一个兄弟节点
console.log(p.previousSibling); // 可能是文本节点(空白字符)
// 获取后一个兄弟节点
console.log(p.nextSibling);
// 只获取元素兄弟节点
console.log(p.previousElementSibling); // h1元素
console.log(p.nextElementSibling); // null
使用querySelector遍历
// 选择div下的所有p元素
const paragraphs = document.querySelectorAll('div p');
// 选择.container类的直接子元素p
const directParagraphs = document.querySelectorAll('.container > p');
DOM树的修改操作
DOM树不是静态的,可以通过JavaScript动态修改:
创建和添加节点
// 创建新元素
const newDiv = document.createElement('div');
newDiv.textContent = '新创建的div';
// 添加到DOM树中
document.body.appendChild(newDiv);
// 插入到特定位置
const container = document.getElementById('container');
container.insertBefore(newDiv, container.firstChild);
删除和替换节点
// 删除节点
const oldP = document.querySelector('p');
oldP.parentNode.removeChild(oldP);
// 替换节点
const newP = document.createElement('p');
newP.textContent = '替换后的段落';
container.replaceChild(newP, container.querySelector('p'));
修改属性和内容
// 修改属性
const img = document.createElement('img');
img.src = 'image.jpg';
img.alt = '示例图片';
// 修改样式
img.style.border = '1px solid #000';
// 修改类名
img.className = 'thumbnail';
img.classList.add('active'); // 更现代的写法
// 修改内容
const heading = document.querySelector('h1');
heading.innerHTML = '修改后的<em>标题</em>';
heading.textContent = '纯文本标题'; // 不解析HTML
DOM树的性能优化
频繁操作DOM会影响页面性能,以下是一些优化建议:
减少重排和重绘
// 不好的做法 - 多次修改样式导致多次重排
const element = document.getElementById('myElement');
element.style.width = '100px';
element.style.height = '200px';
element.style.margin = '10px';
// 好的做法 - 使用cssText或类名一次性修改
element.style.cssText = 'width:100px; height:200px; margin:10px;';
// 或者
element.className = 'new-style';
使用文档片段
// 创建文档片段
const fragment = document.createDocumentFragment();
// 批量添加节点到片段
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `项目 ${i}`;
fragment.appendChild(li);
}
// 一次性添加到DOM
document.getElementById('list').appendChild(fragment);
事件委托
// 不好的做法 - 为每个列表项单独添加事件监听
const items = document.querySelectorAll('li');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// 好的做法 - 利用事件冒泡在父元素上监听
document.getElementById('list').addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
handleClick(event);
}
});
虚拟DOM的概念
现代前端框架如React、Vue都使用了虚拟DOM技术来提高性能:
// 简化的虚拟DOM示例
const virtualDom = {
type: 'div',
props: {
id: 'container',
children: [
{
type: 'h1',
props: {
children: '标题'
}
},
{
type: 'p',
props: {
className: 'content',
children: '段落内容'
}
}
]
}
};
// 虚拟DOM的diff算法会找出实际需要更新的部分
function updateDOM(oldVirtualDOM, newVirtualDOM) {
// 比较差异并只更新必要的真实DOM节点
}
虚拟DOM的优势在于:
- 减少直接操作真实DOM的次数
- 批量处理DOM更新
- 提供跨平台能力(如React Native)
DOM与Shadow DOM的区别
Shadow DOM是Web Components的一部分,它允许创建封装的DOM树:
// 创建Shadow DOM示例
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const template = document.createElement('template');
template.innerHTML = `
<style>
p { color: red; }
</style>
<p>Shadow DOM内容</p>
`;
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('my-component', MyComponent);
Shadow DOM的特点:
- 样式封装(外部样式不会影响内部,内部样式不会影响外部)
- DOM隔离
- 通过特定的选择器才能访问内部元素
浏览器渲染过程中的DOM树
DOM树在浏览器渲染过程中扮演重要角色:
- 解析HTML:构建DOM树
- 解析CSS:构建CSSOM树
- 合并DOM和CSSOM:形成渲染树(Render Tree)
- 布局(Layout):计算每个节点的几何信息
- 绘制(Paint):将渲染树绘制到屏幕上
// 观察渲染性能
function measureLayout() {
const element = document.getElementById('myElement');
const start = performance.now();
// 强制同步布局(应避免)
const width = element.offsetWidth;
console.log('布局耗时:', performance.now() - start);
}
现代API对DOM操作的改进
新的JavaScript API让DOM操作更加高效:
MutationObserver
// 监听DOM变化
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
console.log('DOM发生了变化:', mutation.type);
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
characterData: true
});
IntersectionObserver
// 监听元素可见性
const io = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('元素进入视口:', entry.target);
}
});
});
document.querySelectorAll('.lazy-load').forEach(el => {
io.observe(el);
});
ResizeObserver
// 监听元素尺寸变化
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
console.log('新尺寸:', entry.contentRect);
}
});
ro.observe(document.getElementById('resizable'));