怎样添加、移除、移动、复制、创建和查找节点

53 阅读2分钟

一、DOM节点概述

DOM(文档对象模型)将HTML文档表示为节点树,每个部分都是一个节点:

  • 元素节点:<div><p>
  • 属性节点:classid
  • 文本节点:元素内的文本内容
  • 注释节点:<!-- 注释 -->

二、创建节点

1. 创建元素节点

// 创建div元素
const div = document.createElement('div');

// 创建带属性的元素
const link = document.createElement('a');
link.href = 'https://example.com';
link.textContent = '点击我';

2. 创建文本节点

const text = document.createTextNode('这是一段文本');

// 通常配合元素使用
const paragraph = document.createElement('p');
paragraph.appendChild(text);

3. 创建注释节点

const comment = document.createComment('这是一个注释');

4. 创建文档片段(性能优化)

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);

三、添加节点

1. appendChild()

const parent = document.getElementById('container');
const child = document.createElement('div');
parent.appendChild(child); // 添加到末尾

2. insertBefore()

const parent = document.getElementById('container');
const newChild = document.createElement('div');
const referenceChild = document.getElementById('reference');

// 在referenceChild之前插入
parent.insertBefore(newChild, referenceChild);

3. insertAdjacentHTML/Element/Text()

const target = document.getElementById('target');

// 四种位置选择
target.insertAdjacentHTML('beforebegin', '<div>前面插入</div>'); // 元素之前
target.insertAdjacentHTML('afterbegin', '<div>内部开头插入</div>'); // 元素内部开头
target.insertAdjacentHTML('beforeend', '<div>内部末尾插入</div>'); // 元素内部末尾
target.insertAdjacentHTML('afterend', '<div>后面插入</div>'); // 元素之后

// insertAdjacentElement 和 insertAdjacentText 用法类似
const newElement = document.createElement('span');
target.insertAdjacentElement('beforeend', newElement);

4. append() 和 prepend()(现代方法)

const container = document.getElementById('container');

// 末尾添加多个节点
container.append('文本', document.createElement('br'), '更多内容');

// 开头添加
container.prepend('这是开头内容');

四、移除节点

1. removeChild()

const parent = document.getElementById('parent');
const child = document.getElementById('child');

// 方法1
parent.removeChild(child);

// 方法2:通过父节点查找
const childToRemove = document.getElementById('child-to-remove');
childToRemove.parentNode.removeChild(childToRemove);

2. remove()(现代方法)

const element = document.getElementById('element-to-remove');
element.remove(); // 直接从DOM中移除

3. 移除所有子节点

const container = document.getElementById('container');

// 方法1:清空innerHTML
container.innerHTML = '';

// 方法2:循环移除(性能更好)
while(container.firstChild) {
    container.removeChild(container.firstChild);
}

// 方法3:使用replaceChildren
container.replaceChildren(); // 清空所有子节点

五、移动节点

移动节点本质上是移除再添加到新位置:

// 将节点移动到新位置
const nodeToMove = document.getElementById('movable');
const newParent = document.getElementById('new-parent');

// 方法1:直接添加到新位置(自动从原位置移除)
newParent.appendChild(nodeToMove);

// 方法2:插入到特定位置
const referenceNode = document.getElementById('reference');
newParent.insertBefore(nodeToMove, referenceNode);

// 方法3:使用insertAdjacentElement移动
const target = document.getElementById('target');
target.insertAdjacentElement('beforebegin', nodeToMove);

六、复制节点

1. cloneNode()

const original = document.getElementById('original');

// 浅拷贝:只复制节点本身,不复制子节点
const shallowClone = original.cloneNode(false);

// 深拷贝:复制节点及其所有子节点
const deepClone = original.cloneNode(true);

// 修改克隆节点的ID(避免重复)
deepClone.id = 'original-clone';

// 添加到DOM
document.body.appendChild(deepClone);

2. 克隆并修改

function cloneAndModify(original, modifications) {
    const clone = original.cloneNode(true);
    
    // 应用修改
    Object.keys(modifications).forEach(key => {
        if(key === 'attributes') {
            // 修改属性
            Object.keys(modifications.attributes).forEach(attr => {
                clone.setAttribute(attr, modifications.attributes[attr]);
            });
        } else if(key === 'textContent') {
            // 修改文本内容
            clone.textContent = modifications.textContent;
        } else if(key === 'children') {
            // 处理子节点
            modifications.children.forEach((childMod, index) => {
                if(clone.children[index]) {
                    const childClone = cloneAndModify(childMod.element, childMod.modifications);
                    clone.children[index].replaceWith(childClone);
                }
            });
        }
    });
    
    return clone;
}

// 使用示例
const originalItem = document.querySelector('.list-item');
const clonedItem = cloneAndModify(originalItem, {
    attributes: { id: 'new-item', class: 'list-item cloned' },
    textContent: '新的内容'
});

七、查找节点

1. 经典方法

// 通过ID
const element = document.getElementById('myId');

// 通过类名(返回HTMLCollection)
const elements = document.getElementsByClassName('myClass');

// 通过标签名(返回HTMLCollection)
const divs = document.getElementsByTagName('div');

// 通过name属性
const forms = document.getElementsByName('username');

2. Query Selector方法(推荐)

// 查找单个元素
const firstDiv = document.querySelector('div');
const specialDiv = document.querySelector('#special-id');
const redItem = document.querySelector('.item.red');

// 查找多个元素(返回NodeList)
const allDivs = document.querySelectorAll('div');
const allItems = document.querySelectorAll('.item');
const allInputs = document.querySelectorAll('input[type="text"]');

3. 遍历DOM树

const element = document.getElementById('container');

// 获取父节点
const parent = element.parentNode;
const parentElement = element.parentElement;

// 获取子节点
const children = element.children; // 只包含元素节点
const childNodes = element.childNodes; // 包含所有节点类型

// 第一个和最后一个子节点
const firstChild = element.firstChild;
const firstElementChild = element.firstElementChild;
const lastChild = element.lastChild;
const lastElementChild = element.lastElementChild;

// 兄弟节点
const nextSibling = element.nextSibling;
const nextElementSibling = element.nextElementSibling;
const previousSibling = element.previousSibling;
const previousElementSibling = element.previousElementSibling;

4. 高级查找技巧

// 查找特定内容的元素
const elementsWithText = Array.from(document.querySelectorAll('*')).filter(
    el => el.textContent.includes('特定文本')
);

// 查找最近的祖先元素
const closestForm = document.getElementById('input').closest('form');

// 检查元素是否匹配选择器
const isDiv = element.matches('div'); // 返回true/false

// 在特定元素内查找
const container = document.getElementById('container');
const innerDivs = container.querySelectorAll('div');

八、综合示例

<!DOCTYPE html>
<html>
<head>
    <style>
        .item { padding: 10px; margin: 5px; border: 1px solid #ccc; }
        .active { background-color: yellow; }
    </style>
</head>
<body>
    <div id="container">
        <div class="item">项目1</div>
        <div class="item">项目2</div>
        <div class="item">项目3</div>
    </div>
    
    <button id="addBtn">添加项目</button>
    <button id="moveBtn">移动项目</button>
    <button id="removeBtn">移除最后一个</button>
    <button id="cloneBtn">克隆第一个</button>
    
    <script>
        // 获取元素
        const container = document.getElementById('container');
        const addBtn = document.getElementById('addBtn');
        const moveBtn = document.getElementById('moveBtn');
        const removeBtn = document.getElementById('removeBtn');
        const cloneBtn = document.getElementById('cloneBtn');
        
        // 添加新项目
        addBtn.addEventListener('click', () => {
            const newItem = document.createElement('div');
            newItem.className = 'item';
            newItem.textContent = `项目 ${container.children.length + 1}`;
            
            // 插入到容器末尾
            container.appendChild(newItem);
        });
        
        // 移动第一个项目到最后
        moveBtn.addEventListener('click', () => {
            const firstItem = container.querySelector('.item');
            if(firstItem) {
                container.appendChild(firstItem); // 移动到最后
            }
        });
        
        // 移除最后一个项目
        removeBtn.addEventListener('click', () => {
            const lastItem = container.lastElementChild;
            if(lastItem && lastItem.classList.contains('item')) {
                lastItem.remove();
            }
        });
        
        // 克隆第一个项目并修改
        cloneBtn.addEventListener('click', () => {
            const firstItem = container.querySelector('.item');
            if(firstItem) {
                const clonedItem = firstItem.cloneNode(true);
                clonedItem.textContent = '克隆的: ' + clonedItem.textContent;
                clonedItem.classList.add('active');
                
                // 插入到第一个项目后面
                firstItem.insertAdjacentElement('afterend', clonedItem);
            }
        });
        
        // 查找并高亮包含特定文本的项目
        function highlightItemsWithText(text) {
            const items = container.querySelectorAll('.item');
            items.forEach(item => {
                if(item.textContent.includes(text)) {
                    item.classList.add('active');
                } else {
                    item.classList.remove('active');
                }
            });
        }
        
        // 使用查找功能
        setTimeout(() => {
            highlightItemsWithText('项目2');
        }, 2000);
    </script>
</body>
</html>

九、性能优化建议

  1. 批量操作:使用文档片段或innerHTML批量操作DOM
  2. 离线操作:在内存中完成操作再添加到DOM
  3. 缓存查询结果:避免重复查询DOM
  4. 事件委托:在父元素上监听事件,而不是每个子元素
  5. 避免强制同步布局:避免在循环中读取和写入布局属性
// 不好的做法:多次重排
for(let i = 0; i < 100; i++) {
    element.style.width = i + 'px'; // 写操作,触发重排
    const width = element.offsetWidth; // 读操作,触发重排
}

// 好的做法:批量操作
const fragment = document.createDocumentFragment();
for(let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    fragment.appendChild(div);
}
container.appendChild(fragment);

十、兼容性注意事项

  1. 旧版IE支持:部分方法如remove()append()prepend()在旧版IE中不支持
  2. NodeList与HTMLCollection:NodeList有forEach方法,HTMLCollection没有
  3. polyfill方案:对于不支持的方法,可以使用polyfill或传统方法替代

通过掌握这些DOM节点操作方法,你可以灵活地操作网页内容,创建动态交互效果。