高效手动操作dom 注意点

清空div 使用

replaceChildren
div.replaceChildren()

  • 直接清空所有子节点,性能高效。
  • 不会影响 div 本身的样式、类、ID 或其他属性。
  • 保留占位(div 本身仍在 DOM 树中)。
  • 比 innerHTML = '' 更安全,不会触发 HTML 解析。
    不推荐使用以下的
 div.textContent= '' 
 div.innerHTML = '' 
// 循环删除子节点 效率低下
 while (div.firstChild) {
    div.removeChild(div.firstChild);
 }

DocumentFragment

使用document.querySelector() docuemnt.querySelectorAll()

替代 getElementById getElementsByTagName
querySelectorquerySelectorAll 比传统的 getElementByIdgetElementsByTagName 更灵活。

const items = document.querySelectorAll('.item');
items.forEach(item => item.classList.add('active'));

缓存查询的dom

重复查询 DOM 元素会增加开销,建议缓存结果。

// 低效
document.querySelector('.container').innerHTML = 'Content';
document.querySelector('.container').classList.add('show');

// 高效
const container = document.querySelector('.container');
container.innerHTML = 'Content';
container.classList.add('show');

使用closest 和matches

closest 用于查找最近的祖先元素,matches 用于检查元素是否符合某个选择器,简化事件委托。

document.addEventListener('click', e => {
  if (e.target.matches('.btn')) {
    console.log('Button clicked');
  }
  const parent = e.target.closest('.container');
  if (parent) {
    console.log('Found parent container');
  }
});

使用模板字符串+ insertAdjacentHTML/innerHTML

  • 使用dataSet classList insertAdjacentHTML
    比直接操作 className 更安全、简洁。

    element.classList.add('active');
    element.classList.remove('inactive');
    element.classList.toggle('visible');
    
    element.dataset.id = '123';
    console.log(element.dataset.id); // '123'
    

    innerHTML 更灵活,允许在指定位置插入 HTML。

    element.insertAdjacentHTML('beforeend', '<div>New content</div>');
    
  • repalceChidren
    快速替换所有子节点,性能优于清空 innerHTML 再添加。

    const parent = document.querySelector('.container');
    const newChild = document.createElement('div');
    parent.replaceChildren(newChild);
    

异步操作DOM

对于大型 DOM 操作,使用 requestAnimationFramesetTimeout 分割任务,避免阻塞主线程。

function updateLargeList(items) {
  let index = 0;
  function processChunk() {
    const fragment = document.createDocumentFragment();
    for (let i = 0; i < 100 && index < items.length; i++, index++) {
      const li = document.createElement('li');
      li.textContent = items[index];
      fragment.appendChild(li);
    }
    document.querySelector('ul').appendChild(fragment);
    if (index < items.length) {
      requestAnimationFrame(processChunk);
    }
  }
  requestAnimationFrame(processChunk);
}

事件委托

通过事件冒泡,将事件监听器绑定到父元素,而不是每个子元素,减少内存占用。

document.querySelector('ul').addEventListener('click', e => {
  if (e.target.tagName === 'LI') {
    console.log(e.target.textContent);
  }
});

批量修改样式

避免逐个修改样式,而是通过修改 CSS 类或使用 style.cssText 一次性设置。

// 低效
element.style.color = 'red';
element.style.fontSize = '16px';

// 高效
element.classList.add('new-class');
// 或
element.style.cssText = 'color: red; font-size: 16px;';

避免不必要的 DOM 操作

  • 检查元素是否存在
    在操作元素前,确保其存在,避免错误。

    const element = document.querySelector('.item');
    if (element) {
      element.textContent = 'Updated';
    }
    
  • 避免重复操作
    检查是否需要重复执行相同的操作。

    if (!element.classList.contains('active')) {
      element.classList.add('active');
    }
    

使用 Web Components 或框架

对于复杂的 DOM 操作,考虑使用 Web Components 或前端框架(如 React、Vue、Svelte)。它们通过封装和虚拟 DOM 优化了性能和代码可维护性。

MutationObserve

/**
 * 观察指定容器中是否出现包含特定类名的元素,并执行回调函数
 * @param {HTMLElement} container - 要观察的容器元素
 * @param {string} targetClass - 要检测的类名
 * @param {Function} callback - 检测到目标元素时执行的回调函数,接收找到的元素作为参数
 * @param {MutationObserverInit} [config] - 可选的 MutationObserver 配置,默认为 { childList: true, subtree: true }
 * @returns {MutationObserver} - 返回 MutationObserver 实例,以便后续可手动断开观察
 */
export function observeClassInContainer(container, targetClass, callback, config = { childList: true, subtree: true }) {
  // 验证输入
  if (!(container instanceof HTMLElement)) {
      throw new Error('container 必须是一个有效的 HTML 元素');
  }
  if (typeof targetClass !== 'string' || targetClass.trim() === '') {
      throw new Error('targetClass 必须是一个非空字符串');
  }
  if (typeof callback !== 'function') {
      throw new Error('callback 必须是一个函数');
  }

  // 创建 MutationObserver 回调
  const observerCallback = (mutationsList, observer) => {
      for (const mutation of mutationsList) {
          if (mutation.type === 'childList') {
              mutation.addedNodes.forEach(node => {
                  if (node.nodeType === Node.ELEMENT_NODE) {
                      // 检查当前节点
                      if (node.classList.contains(targetClass)) {
                          callback(node);
                      }
                      // 检查所有子代
                      const elements = node.querySelectorAll(`.${targetClass}`);
                      elements.forEach(element => callback(element));
                  }
              });
          }
      }
  };

  // 初始化并启动 MutationObserver
  const observer = new MutationObserver(observerCallback);
  observer.observe(container, config);

  return observer;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容