📚 现代前端开发 目录
阶段1:Web基础
第 6 / 35 课

DOM操作与事件

与页面交互的艺术

📖 核心概念

💻 代码实现

DOM操作实战 ✅ javascript
// 高效DOM操作
class DOMHelper {
  static create(tag, attrs = {}, children = []) {
    const el = document.createElement(tag);
    Object.entries(attrs).forEach(([key, val]) => {
      if (key === 'className') el.className = val;
      else if (key === 'style' && typeof val === 'object') {
        Object.assign(el.style, val);
      }
      else if (key.startsWith('on')) {
        el.addEventListener(key.slice(2).toLowerCase(), val);
      }
      else if (key === 'dataset') {
        Object.entries(val).forEach(([k, v]) => el.dataset[k] = v);
      }
      else el.setAttribute(key, val);
    });
    children.forEach(child => {
      el.appendChild(typeof child === 'string'
        ? document.createTextNode(child) : child);
    });
    return el;
  }
}

// 使用DocumentFragment批量操作
function renderList(items) {
  const fragment = document.createDocumentFragment();
  items.forEach(item => {
    const li = DOMHelper.create('li', { className: 'item' }, [item]);
    fragment.appendChild(li);
  });
  document.getElementById('list').appendChild(fragment);
}

// IntersectionObserver - 懒加载
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.add('loaded');
      observer.unobserve(img);
    }
  });
}, { rootMargin: '200px' });

document.querySelectorAll('img[data-src]')
  .forEach(img => observer.observe(img));

// MutationObserver - 监听DOM变化
const mutObs = new MutationObserver(mutations => {
  mutations.forEach(m => {
    if (m.type === 'childList') {
      console.log('子节点变化:', m.addedNodes, m.removedNodes);
    }
  });
});
mutObs.observe(document.body, { childList: true, subtree: true });
事件系统 ✅ javascript
// 事件委托 - 高效处理列表点击
document.getElementById('todo-list').addEventListener('click', (e) => {
  const target = e.target.closest('[data-action]');
  if (!target) return;

  const action = target.dataset.action;
  const id = target.closest('[data-id]').dataset.id;

  switch (action) {
    case 'delete': deleteTodo(id); break;
    case 'toggle': toggleTodo(id); break;
    case 'edit':   editTodo(id);   break;
  }
});

// 自定义事件系统
class EventBus {
  #listeners = new Map();

  on(event, callback, options = {}) {
    if (!this.#listeners.has(event)) {
      this.#listeners.set(event, new Set());
    }
    this.#listeners.get(event).add({ callback, ...options });
    return () => this.off(event, callback); // 返回取消函数
  }

  off(event, callback) {
    this.#listeners.get(event)?.delete(
      ...this.#listeners.get(event)?.filter(l => l.callback === callback) || []
    );
  }

  emit(event, detail) {
    this.#listeners.get(event)?.forEach(({ callback, once }) => {
      callback(detail);
      if (once) this.off(event, callback);
    });
  }

  once(event, callback) {
    return this.on(event, callback, { once: true });
  }
}

// 使用
const bus = new EventBus();
bus.on('user:login', (user) => console.log('登录:', user));
bus.emit('user:login', { name: '张三' });

// 防抖与节流
function debounce(fn, ms) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), ms);
  };
}

function throttle(fn, ms) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= ms) {
      last = now;
      fn(...args);
    }
  };
}

// 实际应用
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
  fetchResults(e.target.value);
}, 300));

window.addEventListener('scroll', throttle(() => {
  updateScrollPosition();
}, 100));

🔍 DOM操作性能要点

频繁的DOM操作是前端性能问题的常见根源。核心优化原则:减少DOM访问次数、批量修改、使用DocumentFragment。在React等框架中,虚拟DOM自动处理了这些问题,但理解底层原理有助于写出更高效的代码。

🎯 练习任务

🏆 成就解锁
DOM操控者 — 精通DOM操作与事件处理,构建流畅的页面交互