JavaScript/이벤트 처리

JavaScript 이벤트 객체와 이벤트 위임 - 핵심 가이드

코딩하는 패션이과생 2025. 6. 24. 22:44
반응형

이벤트 객체란?

이벤트 객체(Event Object)는 이벤트가 발생했을 때 자동으로 생성되는 객체로, 이벤트에 대한 상세한 정보를 담고 있습니다. 이벤트 핸들러 함수의 첫 번째 매개변수로 전달됩니다.

🔍 기본 사용법

// 이벤트 객체 받기
button.addEventListener('click', function(event) {
    console.log('이벤트 객체:', event);
    console.log('클릭된 요소:', event.target);
    console.log('이벤트 타입:', event.type);
});

// 화살표 함수에서도 동일
button.addEventListener('click', (e) => {
    console.log('이벤트:', e);
});

이벤트 객체 주요 속성

이벤트 객체는 이벤트에 대한 유용한 정보와 메서드를 제공합니다.

핵심 속성들

document.addEventListener('click', function(event) {
    // 이벤트 대상
    console.log('클릭된 요소:', event.target);
    console.log('이벤트 등록된 요소:', event.currentTarget);

    // 이벤트 정보
    console.log('이벤트 타입:', event.type); // 'click'
    console.log('발생 시간:', event.timeStamp);

    // 마우스 위치
    console.log('화면 좌표:', event.clientX, event.clientY);
    console.log('페이지 좌표:', event.pageX, event.pageY);

    // 키 정보 (키보드 이벤트)
    console.log('눌린 키:', event.key);
    console.log('수식키:', event.ctrlKey, event.shiftKey, event.altKey);
});

이벤트 제어 메서드

// 기본 동작 방지
document.getElementById('link').addEventListener('click', function(event) {
    event.preventDefault(); // 링크 이동 방지
    console.log('링크 클릭되었지만 이동하지 않음');
});

// 이벤트 전파 중단
document.getElementById('button').addEventListener('click', function(event) {
    event.stopPropagation(); // 부모로 전파 방지
    console.log('버튼만 클릭, 부모 이벤트 실행 안됨');
});

// 즉시 전파 중단
element.addEventListener('click', function(event) {
    event.stopImmediatePropagation(); // 같은 요소의 다른 리스너도 실행 안됨
});

target vs currentTarget

<div id="parent">
  <button id="child">클릭하세요</button>
</div>
document.getElementById('parent').addEventListener('click', function(event) {
    console.log('target:', event.target.id);         // 실제 클릭된 요소: 'child'
    console.log('currentTarget:', event.currentTarget.id); // 이벤트 등록된 요소: 'parent'
});

이벤트 위임이란?

이벤트 위임(Event Delegation)은 부모 요소에서 자식 요소들의 이벤트를 처리하는 기법입니다. 이벤트 버블링을 활용하여 효율적인 이벤트 처리가 가능합니다.

기본 개념

<!-- 비효율적인 방법: 각 버튼마다 이벤트 리스너 -->
<div id="container">
  <button class="btn" data-action="save">저장</button>
  <button class="btn" data-action="delete">삭제</button>
  <button class="btn" data-action="edit">수정</button>
</div>
// ❌ 비효율적: 각 버튼마다 개별 리스너
document.querySelectorAll('.btn').forEach(button => {
    button.addEventListener('click', function() {
        console.log('버튼 클릭:', this.dataset.action);
    });
});

// ✅ 효율적: 이벤트 위임 사용
document.getElementById('container').addEventListener('click', function(event) {
    if (event.target.classList.contains('btn')) {
        console.log('버튼 클릭:', event.target.dataset.action);
    }
});

이벤트 위임의 장점

  1. 성능 향상: 하나의 이벤트 리스너로 여러 요소 처리
  2. 동적 요소 지원: 나중에 추가된 요소도 자동으로 이벤트 처리
  3. 메모리 효율: 이벤트 리스너 개수 감소

이벤트 위임 활용법

동적 요소 처리

<div id="todoList">
  <div class="todo-item">
    <span>할 일 1</span>
    <button class="delete-btn">삭제</button>
  </div>
</div>
<button id="addTodo">할 일 추가</button>
let todoList = document.getElementById('todoList');
let addButton = document.getElementById('addTodo');

// 이벤트 위임으로 삭제 버튼 처리
todoList.addEventListener('click', function(event) {
    if (event.target.classList.contains('delete-btn')) {
        // 삭제 버튼 클릭
        let todoItem = event.target.closest('.todo-item');
        todoItem.remove();
        console.log('할 일 삭제됨');
    }
});

// 새 할 일 추가 (이벤트 위임으로 자동 처리됨)
addButton.addEventListener('click', function() {
    let newTodo = document.createElement('div');
    newTodo.className = 'todo-item';
    newTodo.innerHTML = `
        <span>새 할 일</span>
        <button class="delete-btn">삭제</button>
    `;
    todoList.appendChild(newTodo);
});

다중 액션 처리

// 여러 종류의 버튼을 하나의 리스너로 처리
document.getElementById('container').addEventListener('click', function(event) {
    let target = event.target;

    // 버튼별 처리
    if (target.matches('.save-btn')) {
        handleSave(target);
    } else if (target.matches('.delete-btn')) {
        handleDelete(target);
    } else if (target.matches('.edit-btn')) {
        handleEdit(target);
    }
});

function handleSave(button) {
    console.log('저장 처리');
}

function handleDelete(button) {
    let item = button.closest('.item');
    if (confirm('정말 삭제하시겠습니까?')) {
        item.remove();
    }
}

function handleEdit(button) {
    let item = button.closest('.item');
    // 편집 모드 전환
}

실무 활용 예제

1. 테이블 행 클릭 처리

<table id="dataTable">
  <tbody>
    <tr data-id="1">
      <td>사용자 1</td>
      <td>user1@example.com</td>
      <td><button class="edit">수정</button></td>
    </tr>
    <tr data-id="2">
      <td>사용자 2</td>
      <td>user2@example.com</td>
      <td><button class="edit">수정</button></td>
    </tr>
  </tbody>
</table>
document.getElementById('dataTable').addEventListener('click', function(event) {
    let target = event.target;
    let row = target.closest('tr');

    if (target.classList.contains('edit')) {
        // 수정 버튼 클릭
        let userId = row.dataset.id;
        editUser(userId);
    } else if (row && row.dataset.id) {
        // 행 클릭 (버튼 제외)
        if (!target.matches('button')) {
            let userId = row.dataset.id;
            viewUserDetails(userId);
        }
    }
});

function editUser(id) {
    console.log('사용자 수정:', id);
}

function viewUserDetails(id) {
    console.log('사용자 상세 보기:', id);
}

2. 모달 창 관리

// 모든 모달을 하나의 리스너로 관리
document.body.addEventListener('click', function(event) {
    let target = event.target;

    // 모달 열기
    if (target.dataset.modal) {
        let modalId = target.dataset.modal;
        openModal(modalId);
    }

    // 모달 닫기
    if (target.classList.contains('modal-close') || 
        target.classList.contains('modal-overlay')) {
        closeModal(event.target.closest('.modal'));
    }
});

function openModal(modalId) {
    let modal = document.getElementById(modalId);
    if (modal) {
        modal.style.display = 'block';
    }
}

function closeModal(modal) {
    if (modal) {
        modal.style.display = 'none';
    }
}

3. 폼 검증 시스템

document.addEventListener('input', function(event) {
    let target = event.target;

    // 이메일 필드 검증
    if (target.type === 'email') {
        validateEmail(target);
    }

    // 비밀번호 필드 검증
    if (target.type === 'password') {
        validatePassword(target);
    }

    // 숫자 필드 검증
    if (target.type === 'number') {
        validateNumber(target);
    }
});

function validateEmail(input) {
    let isValid = input.value.includes('@');
    toggleValidationClass(input, isValid);
}

function validatePassword(input) {
    let isValid = input.value.length >= 6;
    toggleValidationClass(input, isValid);
}

function validateNumber(input) {
    let isValid = !isNaN(input.value) && input.value !== '';
    toggleValidationClass(input, isValid);
}

function toggleValidationClass(input, isValid) {
    input.classList.toggle('valid', isValid);
    input.classList.toggle('invalid', !isValid);
}

4. 키보드 단축키 시스템

document.addEventListener('keydown', function(event) {
    // Ctrl 또는 Cmd 키와 함께 사용되는 단축키
    if (event.ctrlKey || event.metaKey) {
        switch(event.key) {
            case 's':
                event.preventDefault();
                saveDocument();
                break;
            case 'z':
                event.preventDefault();
                if (event.shiftKey) {
                    redo();
                } else {
                    undo();
                }
                break;
            case 'n':
                event.preventDefault();
                createNewDocument();
                break;
        }
    }

    // ESC 키로 모달 닫기
    if (event.key === 'Escape') {
        let openModal = document.querySelector('.modal:not([style*="display: none"])');
        if (openModal) {
            closeModal(openModal);
        }
    }
});

function saveDocument() {
    console.log('문서 저장됨');
}

function undo() {
    console.log('실행 취소');
}

function redo() {
    console.log('다시 실행');
}

마무리

이벤트 객체이벤트 위임은 효율적인 JavaScript 이벤트 처리의 핵심입니다. 이 두 개념을 잘 활용하면 성능이 좋고 유지보수하기 쉬운 웹 애플리케이션을 만들 수 있습니다.
이벤트 객체와 위임을 마스터하면 더 효율적이고 확장 가능한 이벤트 처리 시스템을 구축할 수 있습니다!