이벤트 처리에서 옵저버와 이벤트 핸들러의 차이를 딥다이브 해보자!
개요

최근 옵저버와 이벤트 핸들러의 차이에 대해서 크게 신경쓰지 않다가, 최근에 크게 배우게 되는 일이 있었습니다.
다만, 모호한 부분이 있었어서 이 참에 다시금 정리하고자 글을 작성하게 되었습니다.
들어가기 전에..
한번쯤 떠올려보면 어떨까합니다. 이벤트 핸들러는 많이 사용해보셨을 것으로 예상되는데요.
그렇다면, 옵저버는 언제 사용해보셨나요?
- 무한 스크롤을 구현할 때 쓰는 InterSection Observer?
- 화면의 사이즈 조절을 감지할 때 쓰는 Resize Observer?
그렇다면, 여러분들은 이런 옵저버들을 왜 쓰셨나요?
Event Handler를 사용했을 때와 달리 어떤 장점이 있으셨나요?
한번쯤 떠올리고 다이브해봅시다!
옵저버(Observer)란 무엇인가?
여러분 옵저버란 무엇일까요?
혹시 어디서 들어보시진 않으셨나요?

스타크래프트에서 들어보셨다면.. 얼추 비슷하게 다가가셨습니다 ㅎㅎ.
옵저버는 말 그대로 "관찰자(Observer)"
입니다.
정확히는 "관찰자 패턴(Observer Pattern)"
에 기반한 메커니즘이지요.
그러면 여기서 무엇을 관찰할까요?
정답은 특정 대상의 상태 변화를 관찰합니다.
특정 대상의 상태 변화를 지속적으로 관찰하고, 변화가 감지되면 미리 등록된 콜백 함수를 실행하는 방식으로 동작하는게 옵저버입니다.
옵저버의 핵심 특징은 비동기적이고 지속적인 관찰입니다.
한 번 등록하면 조건이 충족될 때마다 자동으로 콜백이 실행되며, 개발자가 직접 상태를 확인할 필요가 없죠.
마치 보안 카메라가 움직임을 감지하면 자동으로 멈추는 것과 비슷합니다.
한번 IntersectionObserver를 예로 들어볼까요?
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('요소가 화면에 나타났습니다!');
// 여기서 실행되는 콜백은 브랑줘의 최적화된 타이밍에 따라 실행됩니다.
}
});
});
observer.observe(document.querySelector('.target'));
위 코드를 많이들 사용하셨을거라고 생각되어요. 특정 요소가 화면 상에 감지되면, 콜백을 실행시 키는 코드입니다.
여기서 다음 부분이 보이시나요?
observer.observe();
옵저버에게 어떤 것을 관찰하라고 시키는 겁니다. 여기서는 선택된 요소의 상태 변화를 감지하는 거라고 볼 수 잇겠네요.
IntersectionObserver는 DOM 요소가 뷰포트나 다른 요소와 교차하는 상황을 관찰합니다.
스크롤을 하거나, 요소의 위치가 변경될 때마다 자동으로 교차 상태를 확인하고, 조건에 맞으면 콜백을 실 행하는 방식이죠.
이해가 안되어도 괜찮습니다. 이런게 있다 정도만 봐둡시다 ㅎㅎ.
이벤트 핸들러(Event Handler)란 무엇인가?
이벤트 핸들러는 이벤트 기반 프로그래밍의 핵심 요소입니다.
사용자의 클릭, 키보드 입력, 마우스 움직임 등 특정 이벤트가 발생했을 때 즉시 반응하여 콜백 함수를 실행합니다.
여기서의 핵심은 "즉시" 입니다. 이에 주목해주세요.
이벤트 핸들러의 특징은 즉각적이고 반응적인 실행입니다.
이벤트가 발생하는 순간 바로 콜백이 실행되며, 이벤트의 세부 정보(어떤 키를 눌렀는지, 어디를 클릭했는지 등)를 콜백 함수에 전달합니다.
// 이벤트 핸들러 예제
document.addEventListener('click', (event) => {
console.log('클릭 이벤트 발생!', event.target);
});
이벤트 핸들러에 대해서는 잘 알 것 같아서 간단히만 언급하고 넘어가겠습니다 ㅎㅎ.
이벤트 핸들러와 옵저버의 차이
옵저버 vs 이벤트 핸들러 콜백 실행 방식 비교
구분 | 옵저버 (Observer) | 이벤트 핸들러 (Event Handler) |
---|---|---|
실행 타이밍 | 브라우저 최적화 주기에 따라 실행 렌더링 프레임과 동기화 | 이벤트 발생 즉시 실행 사용자 액션과 직접 연결 |
실행 빈도 | 브라우저가 조절 (throttling 내장) 중복 호출 자동 최적화 | 이벤트 발생 횟수와 1:1 대응 빠른 연속 이벤트 시 과도한 호출 가능 |
콜백 큐 처리 | Intersection Observer Task Queue 낮은 우선순위로 처리 | Event Loop의 Task Queue 높은 우선순위로 즉시 처리 |
메인 스레드 블로킹 | 논블로킹 방식 메인 스레드 성능에 미치는 영향 최소 | 블로킹 가능성 복잡한 콜백 시 UI 응답성 저하 |
배치 처리 | 여러 변화를 배치로 묶어서 처리 entries 배열로 전달 | 개별 이벤트마다 별도 처리 각 이벤트는 독립적 |
브라우저 최적화 | 브라우저 엔진 수준에서 최적화 네이티브 구현의 성능 이점 | JavaScript 엔진 수준에서 처리 개발자가 직접 최적화 필요 |
리소스 사용량 | 낮은 CPU 사용률 GPU 가속 활용 가능 | 높은 CPU 사용률 특히 고빈도 이벤트에서 부담 |
예측 가능성 | 실행 시점 예측 어려움 브라우저 내부 스케줄링에 의존 | 실행 시점 예측 가능 이벤트 발생과 동시에 실행 |
디버깅 용이성 | 비동기적 특성으로 디버깅 복잡 콜백 실행 시점 추적 어려움 | 동기적 실행으로 디버깅 용이 이벤트-콜백 관계 명확 |
메모리 관리 | WeakRef 패턴 사용 자동 가비지 컬렉션 지원 | 명시적 메모리 관리 필요 리스너 해제 직접 처리 |
에러 처리 | 에러 발생 시 전체 관찰 중단 없음 개별 entry 단위로 에러 격리 | 에러 발생 시 해당 이벤트만 영향 다른 이벤트 처리에 영향 없음 |
크로스 브라우저 동작 | 표준 스펙 준수 브라우저별 최적화 차이 존재 | 오래된 표준으로 일관성 높음 브라우저별 차이 최소 |
간단하게 요약해보면 위와 같습니다.
감이 잡히시나요? 아직 모호할 것 같아서, 아래 성능 특성 비교를 통해 더 자세히 알아보겠습니다.
성능 특성 비교
조금 더 쉽게 보기 위해서 코드로 살펴볼까요?
- 옵저버
- 이벤트 핸들러
// 최적화된 배치 처리 예제
const observer = new IntersectionObserver((entries) => {
// 여러 요소의 변화를 한 번에 처리
entries.forEach((entry) => {
// 브라우저가 최적화된 타이밍에 실행
console.log(`${entry.target.id}: ${entry.isIntersecting}`);
});
},
{
// 브라우저 최적화 옵션
rootMargin: '50px',
threshold: [0, 0.5, 1],
}
);
// 즉시 실행되는 개별 처리 예제
window.addEventListener('scroll', (event) => {
// 스크롤할 때마다 즉시 실행 (매우 빈번)
console.log('스크롤 이벤트:', event.target.scrollY);
// 수동 최적화 필요
if (Date.now() - lastExecution > 16) {
// 16ms throttling
checkIntersection();
lastExecution = Date.now();
}
});
어때요? 감이 좀 오시나요? 안오셨다고 해도 괜찮습니다 ㅎㅎ.
이제부터 본격적으로 하나씩 탐구해봅시다.
이벤트 핸들러 딥다이브
우선 이벤트 핸들러부터 살펴봅시다.
이벤트 핸들러의 본질적 특성
이벤트 핸들러를 이해하기 위해서는 먼저 이벤트 기반 프로그래밍의 철학을 파악해야 합니다.
이는 무언가가 일어나면 즉시 반응한다는 반응형 프로그래밍의 핵심 개념입니다.
마치 문지기가 문을 두드리면 즉시 문을 열어주는 것처럼, 이벤트 핸들러는 특정 신호(이벤트)를 받으면 미리 정해진 행동(콜백)을 즉시 수행합니다.
이때, "즉시"이라는 개념이 매우 중요합니다. 사용자가 버튼을 클릭하는 순간, 브라우저는 그 클릭을 감지하고 등록된 이벤트 핸들러를 찾아서 바로 실행합니다.
이 과정에서 지연이나 최적화를 위한 대기 시간은 없 습니다.
// 이벤트 핸들러의 기본 구조
button.addEventListener('click', (event) => {
// 클릭이 발생한 바로 그 순간에 이 코드가 실행됨.
console.log('버튼이 클릭되었습니다!', event);
// event 객체에는 클릭에 대한 모든 정보가 담겨 있음.
console.log('클릭 위치: ', event.clientX, event.clientY);
console.log('클릭된 요소: ', event.target);
});
잠깐! 반응형 프로그래밍이란?
**반응형 프로그래밍(Reactive Programming)**은 **데이터 흐름(data stream)**과 **변경 사항 전파(propagation of change)**에 중점을 둔 프로그래밍 패러다임입니다.
쉽게 말해, **'어떤 데이터가 변경되면, 그 변경에 반응해서 관련된 부분들이 자동으로 업데이트되는 방식'**으로 프로그래밍하는 것이라고 생각할 수 있습니다.
반응형 프로그래밍은 다음과 같은 핵심 개념을 갖습니다.
1. 데이터 스트림 (Data Streams / Observables)
- 시간이 지남에 따라 순차적으로 발생하는 데이터의 연속적인 흐름입니다.
- 예: 사용자 클릭 이벤트, 키보드 입력, HTTP 요청, 센서 데이터, 주식 가격 변동 등
- 반응형 프로그래밍에서는 이런 데이터들을 **"옵저버블(Observable)"**이라는 형태로 다룹니다.