이벤트 위임: 효율적인 이벤트 관리

이벤트 위임의 작동 원리

이벤트 위임은 DOM 이벤트의 버블링을 기반으로 합니다. 하위 요소에서 발생한 이벤트가 상위 요소로 전파되는 특성을 활용해, 개별 요소마다 리스너를 붙이지 않고 공통 조상에 하나의 핸들러를 등록합니다. event.target 속성을 통해 실제 이벤트가 발생한 요소를 식별하며, 이를 기반으로 적절한 동작을 수행합니다.

예제: 리스트 항목 선택

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>이벤트 위임 예제</title>
  <style>
    ul { list-style: none; padding: 0; }
    li { padding: 8px; border: 1px solid #ccc; margin: 4px 0; cursor: pointer; }
    li.selected { background-color: #f0c040; }
  </style>
</head>
<body>
  <ul id="menu">
    <li data-action="home">홈</li>
    <li data-action="about">소개</li>
    <li data-action="contact">연락처</li>
  </ul>

  <script>
    // 공통 조상인 ul에 이벤트 핸들러 등록
    const menu = document.getElementById('menu');

    menu.addEventListener('click', function(event) {
      // closest()를 사용해 클릭한 요소의 가장 가까운 li를 찾습니다.
      const item = event.target.closest('li');

      // li가 존재하고, menu 내부에 있는지 확인합니다.
      if (!item || !menu.contains(item)) return;

      // 기존에 선택된 항목이 있다면 선택 해제
      const prevSelected = menu.querySelector('li.selected');
      if (prevSelected) prevSelected.classList.remove('selected');

      // 새로 클릭한 항목에 클래스 추가
      item.classList.add('selected');

      // data-action 속성에 따라 다른 작업 실행
      const action = item.dataset.action;
      if (action === 'home') {
        console.log('홈 페이지로 이동합니다.');
      } else if (action === 'about') {
        console.log('소개 페이지를 엽니다.');
      } else if (action === 'contact') {
        console.log('연락처 정보를 표시합니다.');
      }
    });
  </script>
</body>
</html>
  • <ul id="menu"> 내부의 각 <li>에는 data-action 속성이 할당되어 있어, 어떤 동작을 수행할지 정보를 담고 있습니다.
  • 공통 조상인 menu 요소에 클릭 이벤트 핸들러를 등록하면, 이벤트 버블링을 통해 모든 <li>의 클릭이 이 핸들러로 전달됩니다.
  • event.target.closest('li')를 사용해 클릭된 요소가 직접 <li>가 아니더라도, 가장 가까운 <li> 요소를 찾아내어 그 요소의 data-action 값을 확인합니다.
  • 선택된 항목에 selected 클래스를 추가해 UI 상에서 강조하고, 콘솔 로그를 통해 동작을 시뮬레이션 합니다.

응용: 동적 테이블 행 삭제

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>이벤트 위임: 테이블 행 삭제</title>
  <style>
    table { border-collapse: collapse; width: 100%; max-width: 600px; }
    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
    button { background: #ff4444; color: white; border: none; padding: 5px 10px; cursor: pointer; }
    button:hover { background: #cc0000; }
  </style>
</head>
<body>
  <table id="userTable">
    <thead>
      <tr>
        <th>ID</th>
        <th>이름</th>
        <th>작업</th>
      </tr>
    </thead>
    <tbody>
      <tr data-user-id="101">
        <td>101</td>
        <td>김영수</td>
        <td><button class="delete-btn">삭제</button></td>
      </tr>
      <tr data-user-id="102">
        <td>102</td>
        <td>이민정</td>
        <td><button class="delete-btn">삭제</button></td>
      </tr>
    </tbody>
  </table>
  <button id="addUser">사용자 추가</button>

  <script>
    const table = document.getElementById('userTable');

    // 삭제 버튼 이벤트 위임
    table.addEventListener('click', function(event) {
      const btn = event.target.closest('.delete-btn');
      if (!btn || !table.contains(btn)) return;

      const row = btn.closest('tr');
      const userId = row.dataset.userId;
      row.remove();
      console.log(`사용자 ID ${userId} 삭제됨`);
    });

    // 동적 행 추가 기능
    let nextId = 103;
    document.getElementById('addUser').addEventListener('click', function() {
      const tbody = table.querySelector('tbody');
      const newRow = document.createElement('tr');
      newRow.dataset.userId = nextId;
      newRow.innerHTML = `
        <td>${nextId}</td>
        <td>사용자 ${nextId}</td>
        <td><button class="delete-btn">삭제</button></td>`
      ;
      tbody.appendChild(newRow);
      nextId++;
    });
  </script>
</body>
</html>
  • <table id="userTable">에 이벤트 핸들러를 등록해 모든 .delete-btn 클릭을 처리합니다.
  • event.target.closest('.delete-btn')로 클릭된 삭제 버튼을 식별하고, btn.closest('tr')로 해당 행을 찾아 제거합니다.
  • addUser 버튼으로 동적 행을 추가하며, 추가된 행의 삭제 버튼도 별도 리스너 없이 자동으로 처리됩니다.
  • data-user-id 속성을 통해 삭제된 사용자의 ID를 추적합니다.

이벤트 위임의 장단점

장점

  • 메모리 효율성: 수백 개 요소에 개별 리스너를 붙일 필요 없이 단일 조상 리스너로 관리해 메모리 사용을 최적화.
  • 동적 요소 지원: DOM에 새 요소가 추가되거나 삭제되어도 추가적인 핸들러 관리가 필요 없음.
  • 간결한 코드: 중앙 집중식 관리로 코드가 간소화되고, 유지보수가 쉬워짐.

단점

  • 불필요한 처리 가능성: 이벤트가 빈번히 발생하면 상위 핸들러가 과도한 검증 작업을 수행할 수 있음.
  • 버블링 의존성: focus, blur 같은 비버블링 이벤트에는 적용 불가.