Share
Sign In

이벤트

이벤트 버블링과 캡처링

이벤트 캡처링과 이벤트 버블링은 이벤트 전파 과정에서 발생하는 현상이다. 이벤트 캡처링은 이벤트가 하위 요소로 전파되는 과정을 말하며, 이벤트 버블링은 이벤트가 상위 요소로 전파되는 과정을 말한다. 이해를 돕기 위해 실제 코드 예제를 살펴보자.
이해를 돕기 위해 실제 코드 예제를 살펴보자.
const parent = document.querySelector('.parent'); const child = document.querySelector('.child'); parent.addEventListener('click', () => { console.log('Parent clicked during capturing phase'); }, true); child.addEventListener('click', () => { console.log('Child clicked during capturing phase'); }, true); parent.addEventListener('click', () => { console.log('Parent clicked during bubbling phase'); }); child.addEventListener('click', () => { console.log('Child clicked during bubbling phase'); });

stopPropagation과 preventDefault

이벤트 캡처링과 버블링에서 이벤트 전파를 중단하려면 이벤트 객체의 stopPropagation 메서드를 사용하면 된다. 그러나 이 경우에도 기본 동작이 수행된다. 기본 동작까지 막으려면 이벤트 객체의 preventDefault 메서드를 사용해야 한다.
// 이벤트 캡처링 사용 element.addEventListener('click', () => { console.log('Capturing phase'); }, { capture: true }); // 이벤트 버블링 중단 element.addEventListener('click', (event) => { console.log('Stop bubbling'); event.stopPropagation(); // 전파 중단 event.preventDefault(); // 기본 동작 중단 });

버블링을 통한 이벤트 위임 활용하기

이벤트 위임은 이벤트 핸들러를 부모 요소에 등록하여 자식 요소의 이벤트를 한 곳에서 관리하는 기법이다. 이를 활용하면 동적으로 생성되는 요소에 대한 이벤트 처리도 쉽게 할 수 있다. 예를 들어, 아래와 같이 리스트 아이템에 클릭 이벤트를 적용해보자.
const list = document.querySelector('ul'); list.addEventListener('click', (event) => { if (event.target.tagName === 'LI') { console.log(`${event.target.textContent} clicked`); } });

이벤트를 사용하면서 고려할 점들

이벤트 리스너의 적절한 제거

이벤트 리스너를 올바르게 제거하지 않으면 메모리 누수의 원인이 될 수 있다. 이벤트 리스너를 제거하려면 removeEventListener 메서드를 사용한다. 단, removeEventListener에 전달하는 인수는 addEventListener에 전달한 것과 동일해야 한다.
function handleClick() { console.log('Button clicked'); } button.addEventListener('click', handleClick); button.removeEventListener('click', handleClick);

이벤트 처리 성능 최적화

이벤트 처리에 따른 성능 저하를 방지하기 위해서는 이벤트 리스너를 효율적으로 사용해야 한다. 예를 들어, 이벤트 리스너가 많은 요소에 적용되지 않도록 이벤트 위임 기법을 사용하거나, 필요한 경우에만 이벤트 리스너를 등록하도록 코드를 작성해야 한다. 또한, 이벤트 처리 함수의 실행 시간을 줄이기 위해 가능한 한 간결하게 작성하는 것이 좋다.

이벤트 루프와 이벤트 큐

자바스크립트는 이벤트 루프를 통해 비동기 처리를 수행한다. 이벤트 루프는 콜 스택이 비워질 때마다 이벤트 큐에서 가장 오래된 이벤트를 꺼내어 처리한다. 이벤트 큐에 이벤트가 쌓이는 경우, 이벤트 처리가 지연되거나 프로그램이 정지할 수 있다. 이러한 문제를 해결하려면 이벤트 처리 과정에서 발생하는 비동기 작업을 최적화해야 한다.

메모리 누수 문제

이벤트 리스너를 제대로 제거하지 않으면 메모리 누수가 발생할 수 있다. 메모리 누수는 애플리케이션의 성능 저하를 일으키므로 주의가 필요하다. 이벤트 리스너를 제거할 때는 removeEventListener 메서드를 사용하며, 전달하는 인수는 addEventListener에 전달한 것과 동일해야 한다. 다음과 같이 이벤트 리스너를 적절하게 제거하자.
function handleClick() { console.log('Button clicked'); } button.addEventListener('click', handleClick); button.removeEventListener('click', handleClick);

클로저와 메모리 관리

이벤트 핸들러 내에서 클로저를 사용할 경우 메모리 누수 문제가 발생할 수 있다. 클로저가 참조하는 외부 변수가 메모리에서 해제되지 않기 때문이다. 이 문제를 해결하려면 클로저를 사용하는 대신 이벤트 핸들러 내에서 필요한 데이터를 명시적으로 전달하는 방법을 사용해야 한다. 예를 들어, 다음과 같이 이벤트 핸들러에서 클로저 대신 이벤트 객체를 사용할 수 있다.
button.addEventListener('click', function(event) { const data = event.target.dataset; console.log(data); });

메모리 프로파일링 활용

크롬 개발자 도구의 메모리 탭에서 메모리 프로파일링 기능을 활용하면 메모리 사용량과 메모리 누수를 쉽게 찾을 수 있다. 메모리 프로파일링을 통해 어떤 객체가 메모리에서 해제되지 않고 쌓이는지 확인하고, 필요한 경우 코드를 수정하여 메모리 누수를 방지할 수 있다. 또한, 개발자 도구의 Performance 탭에서 성능 분석을 수행하여 이벤트 처리 과정에서 발생하는 메모리 사용량 변화를 확인할 수 있다.

이벤트 처리 최적화를 위한 요청 지연과 디바운싱

자주 발생하는 이벤트, 예를 들어 스크롤 이벤트나 리사이즈 이벤트의 경우, 이벤트 처리 과정에서 발생하는 비동기 작업을 최적화하기 위해 요청 지연과 디바운싱 기법을 사용할 수 있다. 요청 지연이란 이벤트 처리를 일정 시간 지연시키는 기법이며, 디바운싱은 연속적으로 발생하는 이벤트에 대해 마지막 이벤트만 처리하는 기법이다. 이러한 기법들을 활용하면 이벤트 처리 성능을 향상시킬 수 있다.
// 디바운싱 함수 예시 function debounce(func, wait) { let timeout; return function (...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } const handleScroll = debounce(() => { console.log('Scrolled'); }, 200); window.addEventListener('scroll', handleScroll);

웹 워커를 활용한 병렬 처리

복잡한 이벤트 처리 작업을 수행할 때는 웹 워커를 활용하여 병렬 처리를 수행할 수 있다. 웹 워커는 자바스크립트 코드를 별도의 스레드에서 실행할 수 있게 해주는 API로, 이벤트 처리 작업에 따른 메인 스레드의 부하를 줄이고 성능을 향상시킬 수 있다.
// 웹 워커 생성 const worker = new Worker('worker.js'); // 메인 스레드에서 웹 워커에게 메시지 전달 worker.postMessage({ message: 'Hello, worker!' }); // 웹 워커에서 메시지 받기 worker.addEventListener('message', (event) => { console.log(event.data); // "Hello, main thread!" }); // worker.js self.addEventListener('message', (event) => { console.log(event.data); // "Hello, worker!" self.postMessage({ message: 'Hello, main thread!' }); });

커스텀 이벤트 생성과 활용

커스텀 이벤트를 생성하려면 CustomEvent 객체를 사용한다. CustomEvent 생성자는 이벤트의 이름과 옵션 객체를 인수로 받는다. 생성된 이벤트는 dispatchEvent 메서드를 사용해 발생시킬 수 있다.
const customEvent = new CustomEvent('myEvent', { detail: { message: 'This is a custom event.' } }); element.addEventListener('myEvent', (event) => { console.log(event.detail.message); // "This is a custom event." }); element.dispatchEvent(customEvent);

모바일에서 추가로 고려할 점들

Click 딜레이 제거

원래 touchstart와 click간에는 300ms 정도의 딜레이가 존재한다. 그러나 모던 브라우저에서는 모바일 페이지에 최적화된 경우 딜레이를 만들지 않는다. 따라서 뷰포트를 맞춰준다면 딜레이가 사라진다.
<meta name="viewport" content="width=device-width" />

동시 이벤트 발생을 막기 위해 터치 이벤트에 preventDefault 사용

모바일과 데스크톱 환경에서 동시에 작동하는 웹 애플리케이션을 개발할 때는 터치 이벤트와 클릭 이벤트를 모두 고려해야 한다. 이 경우, 터치 이벤트와 마우스 이벤트가 동시에 발생하는 것을 방지하기 위해 touchstart 이벤트 발생 시 이벤트 객체의 preventDefault 메서드를 사용하는 것이 좋다.
element.addEventListener('touchstart', (event) => { console.log('Touch event'); event.preventDefault(); // 마우스 이벤트 동시 발생 방지 }); element.addEventListener('mousedown', () => { console.log('Mouse event'); });
단, 크롬, 파이어폭스 등에서는 스크롤 성능 향상 등을 위해서 touchstart, touchmove의 경우 passive 기본값이 true로 설정된다고 한다. 이 경우 preventDefault가 제대로 동작하지 않으므로, 대신 touchend 의 콜백에서 preventDefault를 호출하기도 한다. (출처는 여기)