创建 DOM 元素

1. 使用 createElement 方法

// 创建各种类型的元素
const div = document.createElement('div');
const span = document.createElement('span');
const button = document.createElement('button');
const input = document.createElement('input');
const img = document.createElement('img');

// 设置元素属性和内容
div.id = 'myDiv';
div.className = 'container';
div.textContent = 'Hello World';

// 设置其他属性
button.setAttribute('type', 'button');
button.setAttribute('data-action', 'submit');
input.setAttribute('placeholder', 'Enter text');
img.setAttribute('src', 'image.jpg');
img.setAttribute('alt', 'Description');

// 设置样式
div.style.color = 'red';
div.style.fontSize = '16px';

2. 使用 createTextNode 创建文本节点

// 创建纯文本节点
const textNode = document.createTextNode('This is a text node');

// 添加到元素中
const p = document.createElement('p');
p.appendChild(textNode);

// 等价于
p.textContent = 'This is a text node';

3. 使用 innerHTML 创建元素(字符串方式)

const container = document.createElement('div');
container.innerHTML = '<p>Hello <strong>World</strong></p>';

// 注意:innerHTML 会解析整个 HTML 字符串并创建相应元素
// 获取创建的子元素
const pElement = container.querySelector('p');
const strongElement = container.querySelector('strong');

4. 使用 createDocumentFragment 创建文档片段

// 创建文档片段(轻量级容器,不在 DOM 树中)
const fragment = document.createDocumentFragment();

// 在片段中创建多个元素
for (let i = 0; i < 5; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i + 1}`;
    fragment.appendChild(li);
}

// 将整个片段插入 DOM(只触发一次重排)
document.getElementById('myList').appendChild(fragment);

插入 DOM 元素

1. 基本插入方法

appendChild

// 将元素添加到父元素的末尾
parentElement.appendChild(newElement);

// 示例
const container = document.getElementById('container');
const newDiv = document.createElement('div');
newDiv.textContent = 'New Element';
container.appendChild(newDiv);

// 可以链式调用
container
    .appendChild(document.createElement('div'))
    .appendChild(document.createTextNode('Nested text'));

insertBefore

// 在指定元素之前插入新元素
parentElement.insertBefore(newElement, referenceElement);

// 示例:在第一个子元素前插入
const firstChild = parentElement.firstChild;
parentElement.insertBefore(newElement, firstChild);

// 插入到末尾(等同于 appendChild)
parentElement.insertBefore(newElement, null);

// 在最后一个子元素前插入
const lastChild = parentElement.lastChild;
parentElement.insertBefore(newElement, lastChild);

replaceChild

// 替换现有子元素
parentElement.replaceChild(newElement, oldElement);

// 示例
const oldChild = document.getElementById('oldElement');
const newChild = document.createElement('div');
newChild.textContent = 'Replacement Element';
parentElement.replaceChild(newChild, oldChild);

2. 现代插入方法(ES2021+)

append 和 prepend

// 添加到末尾(可添加多个元素或文本)
parentElement.append(newElement1, newElement2, 'Some text');

// 添加到开头
parentElement.prepend(newElement);

// 示例
const container = document.getElementById('container');
const div1 = document.createElement('div');
div1.textContent = 'First';
const div2 = document.createElement('div');
div2.textContent = 'Second';

container.append(div1, div2, ' and some text');
container.prepend('Text at the beginning');

before 和 after

// 在元素之前插入
referenceElement.before(newElement);

// 在元素之后插入
referenceElement.after(newElement);

// 可以插入多个元素
referenceElement.before(element1, element2, 'text node');

// 示例
const targetElement = document.getElementById('target');
const newElement = document.createElement('div');
newElement.textContent = 'Inserted before target';
targetElement.before(newElement);

replaceWith

// 用新元素替换当前元素
oldElement.replaceWith(newElement);

// 可以替换为多个元素
oldElement.replaceWith(element1, element2, 'text');

// 示例
const oldElement = document.getElementById('toBeReplaced');
const newElement = document.createElement('div');
newElement.textContent = 'New replacement element';
oldElement.replaceWith(newElement);

插入优化策略

1. 使用 DocumentFragment 减少重排

// 低效方式:多次操作 DOM
function appendItemsSlow(items, parent) {
    items.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item;
        parent.appendChild(li); // 每次都触发重排
    });
}

// 高效方式:使用 DocumentFragment
function appendItemsFast(items, parent) {
    const fragment = document.createDocumentFragment();
    items.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item;
        fragment.appendChild(li);
    });
    parent.appendChild(fragment); // 只触发一次重排
}

2. 批量操作 DOM

// 低效方式:逐个修改样式
function styleElementsSlow(elements) {
    elements.forEach(el => {
        el.style.color = 'red';
        el.style.fontSize = '16px';
        el.style.margin = '10px';
    });
}

// 高效方式:通过 CSS 类批量操作
function styleElementsFast(elements) {
    elements.forEach(el => {
        el.classList.add('styled-element');
    });
}

// 对应的 CSS
/*
.styled-element {
    color: red;
    font-size: 16px;
    margin: 10px;
}
*/

3. 使用 innerHTML 与 DOM 操作的权衡

// 对于大量静态内容,innerHTML 更快
function createListWithInnerHTML(items) {
    const container = document.getElementById('listContainer');
    let html = '<ul>';
    items.forEach(item => {
        html += `<li>${item}</li>`;
    });
    html += '</ul>';
    container.innerHTML = html;
}

// 对于需要事件监听器的动态内容,DOM 操作更合适
function createListWithDOM(items) {
    const container = document.getElementById('listContainer');
    const ul = document.createElement('ul');
    
    items.forEach(item => {
        const li = document.createElement('li');
        li.textContent = item;
        li.addEventListener('click', handleClick); // 可以添加事件监听器
        ul.appendChild(li);
    });
    
    container.appendChild(ul);
}

4. 虚拟滚动和懒加载

// 对于大量列表项,使用虚拟滚动优化性能
class VirtualList {
    constructor(container, items, itemHeight) {
        this.container = container;
        this.items = items;
        this.itemHeight = itemHeight;
        this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 5;
        
        this.render();
        this.bindScroll();
    }
    
    render() {
        // 只渲染可见的项目
        const startIndex = Math.floor(this.container.scrollTop / this.itemHeight);
        const endIndex = Math.min(startIndex + this.visibleCount, this.items.length);
        
        let html = '';
        for (let i = startIndex; i < endIndex; i++) {
            html += `<div style="height: ${this.itemHeight}px;">${this.items[i]}</div>`;
        }
        
        this.container.innerHTML = html;
    }
    
    bindScroll() {
        this.container.addEventListener('scroll', () => {
            this.render();
        });
    }
}

5. 使用 requestAnimationFrame 优化动画

// 低效的动画方式
function animateElementBad(element) {
    let position = 0;
    function step() {
        position += 2;
        element.style.left = position + 'px';
        if (position < 200) {
            step(); // 可能导致页面卡顿
        }
    }
    step();
}

// 高效的动画方式
function animateElementGood(element) {
    let position = 0;
    function step() {
        position += 2;
        element.style.left = position + 'px';
        if (position < 200) {
            requestAnimationFrame(step); // 浏览器优化的动画帧
        }
    }
    requestAnimationFrame(step);
}

6. 避免强制同步布局

// 低效方式:强制同步布局
function badLayoutExample() {
    const element = document.getElementById('myElement');
    
    // 修改样式
    element.style.width = '200px';
    
    // 立即读取样式(强制浏览器重新计算布局)
    const width = element.offsetWidth;
    
    // 再次修改样式
    element.style.height = '200px';
    
    // 再次读取样式
    const height = element.offsetHeight;
}

// 高效方式:批量读写操作
function goodLayoutExample() {
    const element = document.getElementById('myElement');
    
    // 批量修改样式
    element.style.width = '200px';
    element.style.height = '200px';
    
    // 批量读取样式
    const width = element.offsetWidth;
    const height = element.offsetHeight;
}

移除 DOM 元素

1. 使用 removeChild 方法

// 传统方法:通过父元素移除子元素
const parentElement = document.getElementById('parent');
const childElement = document.getElementById('child');
parentElement.removeChild(childElement);

// 安全的移除方式(检查是否存在)
function safeRemoveChild(parent, child) {
    if (parent.contains(child)) {
        parent.removeChild(child);
    }
}

2. 使用 remove 方法(现代浏览器)

// 现代方法:直接移除元素
const element = document.getElementById('toRemove');
element.remove();

// 移除多个元素
document.querySelectorAll('.to-remove').forEach(el => el.remove());

3. 清空元素内容

// 清空元素的所有子元素
const container = document.getElementById('container');

// 方法1:使用 innerHTML
container.innerHTML = '';

// 方法2:逐个移除子元素
while (container.firstChild) {
    container.removeChild(container.firstChild);
}

// 方法3:使用 removeChild 和 lastChild
while (container.lastChild) {
    container.removeChild(container.lastChild);
}

4. 条件移除元素

// 根据条件移除元素
function removeElementsByCondition(parent, conditionFn) {
    const children = Array.from(parent.children);
    children.forEach(child => {
        if (conditionFn(child)) {
            child.remove();
        }
    });
}

// 示例:移除所有空的 div 元素
removeElementsByCondition(document.body, (element) => {
    return element.tagName === 'DIV' && element.textContent.trim() === '';
});

5. 移除事件监听器

// 移除元素前清理事件监听器(防止内存泄漏)
const button = document.getElementById('myButton');
const handleClick = function() {
    console.log('Button clicked');
};

button.addEventListener('click', handleClick);

// 移除元素时
button.removeEventListener('click', handleClick);
button.remove();

克隆 DOM 元素

1. 使用 cloneNode 方法

// 浅克隆:只克隆元素本身,不包括子元素
const originalElement = document.getElementById('original');
const shallowClone = originalElement.cloneNode(false);

// 深克隆:克隆元素及其所有子元素和文本内容
const deepClone = originalElement.cloneNode(true);

// 插入克隆的元素
document.getElementById('container').appendChild(deepClone);

2. 克隆元素的属性处理

const original = document.getElementById('original');
const clone = original.cloneNode(true);

// 注意:克隆会复制所有属性,包括 id
console.log(clone.id); // 与 original 相同的 id

// 修改克隆元素的 id 以避免重复
clone.id = 'clone-' + original.id;

// 克隆会保留事件监听器吗?不会!
// 需要手动重新添加事件监听器
clone.addEventListener('click', function() {
    console.log('Clone clicked');
});

3. 深度克隆与浅度克隆的区别

<div id="parent">
    <span>Child 1</span>
    <p>Child 2</p>
</div>
const parent = document.getElementById('parent');

// 浅克隆
const shallow = parent.cloneNode(false);
console.log(shallow.children.length); // 0 - 没有子元素

// 深克隆
const deep = parent.cloneNode(true);
console.log(deep.children.length); // 2 - 包含所有子元素
console.log(deep.children[0].textContent); // "Child 1"
console.log(deep.children[1].textContent); // "Child 2"

4. 克隆并修改元素

function cloneAndModify(originalId, modifications) {
    const original = document.getElementById(originalId);
    const clone = original.cloneNode(true);
    
    // 修改属性
    if (modifications.id) {
        clone.id = modifications.id;
    }
    
    if (modifications.className) {
        clone.className = modifications.className;
    }
    
    // 修改内容
    if (modifications.textContent) {
        clone.textContent = modifications.textContent;
    }
    
    // 修改样式
    if (modifications.style) {
        Object.assign(clone.style, modifications.style);
    }
    
    return clone;
}

// 使用示例
const newElement = cloneAndModify('template', {
    id: 'new-item',
    className: 'item cloned-item',
    textContent: 'Cloned Item',
    style: {
        color: 'blue',
        fontWeight: 'bold'
    }
});

document.getElementById('container').appendChild(newElement);

5. 批量克隆元素

function cloneMultipleElements(selector, count, containerId) {
    const original = document.querySelector(selector);
    const container = document.getElementById(containerId);
    
    for (let i = 0; i < count; i++) {
        const clone = original.cloneNode(true);
        clone.id = `${original.id}-${i}`; // 确保唯一 id
        clone.setAttribute('data-index', i);
        container.appendChild(clone);
    }
}

// 使用示例
cloneMultipleElements('.template-item', 5, 'item-container');

6. 克隆时处理特殊属性

function smartClone(element) {
    const clone = element.cloneNode(true);
    
    // 处理表单元素的值
    const originalInputs = element.querySelectorAll('input, textarea, select');
    const cloneInputs = clone.querySelectorAll('input, textarea, select');
    
    originalInputs.forEach((input, index) => {
        const cloneInput = cloneInputs[index];
        if (input.type === 'checkbox' || input.type === 'radio') {
            cloneInput.checked = input.checked;
        } else {
            cloneInput.value = input.value;
        }
    });
    
    // 处理自定义属性
    const dataAttributes = element.dataset;
    Object.keys(dataAttributes).forEach(key => {
        clone.dataset[key] = dataAttributes[key];
    });
    
    return clone;
}

性能监控和调试

1. 使用 Performance API 监控 DOM 操作性能

// 监控 DOM 操作性能
function measureDOMOperation(operation, label) {
    performance.mark(`${label}-start`);
    operation();
    performance.mark(`${label}-end`);
    performance.measure(label, `${label}-start`, `${label}-end`);
    
    const measure = performance.getEntriesByName(label)[0];
    console.log(`${label} took ${measure.duration} milliseconds`);
}

// 使用示例
measureDOMOperation(() => {
    // 执行 DOM 操作
    const container = document.getElementById('container');
    for (let i = 0; i < 1000; i++) {
        const div = document.createElement('div');
        div.textContent = `Item ${i}`;
        container.appendChild(div);
    }
}, 'DOM Insertion');

2. 使用浏览器开发者工具

  • 使用 Chrome DevTools 的 Performance 面板分析重排和重绘
  • 使用 Memory 面板检查内存泄漏
  • 使用 Rendering 工具可视化重排和重绘

这些全面的 DOM 操作策略可以帮助显著提高 Web 应用的性能,特别是在处理大量元素或频繁更新的场景中。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐