您现在的位置是:网站首页 > DOM树结构文章详情

DOM树结构

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元素节点
          • 文本节点"示例页面"
      • body元素节点
        • div元素节点(id="container")
          • h1元素节点
            • 文本节点"标题"
          • p元素节点(class="content")
            • 文本节点"段落内容"

DOM节点类型详解

DOM树由多种不同类型的节点组成,每种节点都有特定的属性和方法。最常见的节点类型包括:

  1. 文档节点(Document):整个文档的根节点
  2. 元素节点(Element):HTML标签对应的节点
  3. 属性节点(Attr):元素的属性
  4. 文本节点(Text):元素或属性中的文本内容
  5. 注释节点(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的优势在于:

  1. 减少直接操作真实DOM的次数
  2. 批量处理DOM更新
  3. 提供跨平台能力(如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的特点:

  1. 样式封装(外部样式不会影响内部,内部样式不会影响外部)
  2. DOM隔离
  3. 通过特定的选择器才能访问内部元素

浏览器渲染过程中的DOM树

DOM树在浏览器渲染过程中扮演重要角色:

  1. 解析HTML:构建DOM树
  2. 解析CSS:构建CSSOM树
  3. 合并DOM和CSSOM:形成渲染树(Render Tree)
  4. 布局(Layout):计算每个节点的几何信息
  5. 绘制(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'));

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步