이번 글에서는 Javascript의 이벤트 관리에 대해 배운점을 정리하려합니다.
우선 이벤트란 무엇인가?
이벤트란 브라우저 상에서 처리해야할(발생한) 특정 사건을 의미합니다.
예를 들어 키보드 입력, 마우스 이동 등이 일어나면 브라우저는 이를 감지하여 특정한 타입의 이벤트를 발생시킵니다.
이벤트 핸들러는 이벤트가 발생했을 때 브라우저에 호출을 위임한 함수입니다.
(이벤트가 발생하면 브라우저에 의해 호출될 함수가 이벤트 핸들러이다.)
이벤트 핸들러를 등록하는 방법은 총 3가지 방법이 있습니다.
이벤트 핸들러 어트리뷰트 값으로 함수 호출문 등의 문(statement)을 할당하면 이벤트 핸들러가 등록된다.
<!DOCTYPE html>
<html>
<body>
<button onclick="sayHi('Hyun')">Click me!</button>
<script>
function sayHi(name) {
console.log(`Hi ${name}.`);
};
</script>
</body>
</html>
주의할 점은 이벤트 핸들러 어트리뷰트 값으로 함수 참조가 아닌 함수 호출문 등의 문을 할당한다는 것이다.
만약 이벤트 핸들러 어트리뷰트 값으로 함수 참조를 할당해야 한다면 이벤트 핸들러에 인수를 전달하기 곤란하다.
// 이벤트 핸들러에 인수를 전달하기 곤란한 경우
<button onclick="sayHi">Click me!</button>
// 이벤트 핸들러 어트리뷰트 값으로 다음과 같이 여러개의 문을 할당할 수 있다.
<button onclick="console.log("Hi! "); console.log("Kim");">Click me!</button>
HTML과 자바스크립트는 관심사가 다르므로 혼재하는 것보다 분리하는게 좋지만 CBD(Component Based Development)방식의 Angular / React / Svelte / Vue.js 같은 프레임워크 / 라이브러리에서는 이벤트 핸들러 어트리뷰트 방식으로 이벤트를 처리한다.
CBD에서는 HTML, CSS, Javascript를 관심사가 다른 개별적인 요소가 아닌, 뷰를 구성하기 위한 구성 요소로 보기 때문이다.
// Angular
<button (click)="handleClick($event)">Click me!</button>
// React
<button onClick={handleClick}>Click me!</button>
// Svelte
<button on:click={handleClick}>Click me!</button>
// Vue.js
<button v-on:click="handleClick($event)">Click me!</button>
이벤트 핸들러 프로퍼티에 함수를 바인딩하면 이벤트 핸들러가 등록된다.
<!DOCTYPE html>
<html>
<body>
<button>Click me!</button>
<script>
const $button = document.querySelector('button');
// 이벤트 핸들러 프로퍼티에 이벤트 핸들러를 바인딩
$button.onclick = function () {
console.log('button click');
};
</script>
</body>
</html>
이벤트 핸들러를 등록하기 위해서는 이벤트를 발생시킬 객체인 이벤트 타깃과 이벤트의 종류를 나타내는 문자열인 이벤트 타입 그리고 이벤트 핸들러를 지정할 필요가 있다.
$button.onclick = function () { ~~
이벤트 타깃 on + 이벤트 타입 이벤트 핸들러
이벤트 핸들러 프로퍼티는 하나의 이벤트 핸들러만 바인딩할 수 있다는 단점이 있다.
앞서 살펴본 어트리뷰트 방식과 프로퍼티 방식은 DOM level 0부터 제공되는 방식이다.
DOM Level에 대해 간략하게 설명하면 다음과 같다.
level 0
인라인 이벤트 모델
기본 이벤트 모델
level 2
MS 인터넷 익스플로어 이벤트 모델
표준 이벤트 모델
EventTarget.addEventListener('eventType', functionName [,useCapture]);
이벤트 타깃 이벤트 타입 이벤트 핸들러capture 사용 여부
addEventListener 메서드의 첫 번째 매개변수에는 이벤트의 종류를 나타내는 문자열인 이벤트 타입을 전달한다.
(on접두사를 생략한다.)
두번째 매개변수에는 이벤트 핸들러를 전달한다.
마지막 매개변수에는 이벤트를 캐치할 이벤트 전파 단계(캡처링 또는 버블링)를 지정한다.
(생략하거나 false를 지정하면 버블링 단계에서 이벤트를 캐치, true를 지정하면 캡처링 단계에서 이벤트를 캐리한다.)
<!DOCTYPE html>
<html>
<body>
<button>Click me!</button>
<script>
const $button = document.querySelector('button');
// 이벤트 핸들러 프로퍼티에 이벤트 핸들러를 바인딩
$button.addEventListener('click', function() {
console.log('[addEventListener 메서드 방식]button click');
});
</script>
</body>
</html>
addEventListener 메서드는 하나 이상의 이벤트 핸들러를 등록할 수 있으며 등록된 순서대로 호출한다.
(단 addEventListener 메서드를 통해 참조가 동일한 이벤트 핸들러를 중복 등록하면 하나의 이벤트 핸들러만 등록된다.)
이벤트 전파
DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파된다. 이를 이벤트 전파라고 한다.
이벤트가 발생하는 순서를 이벤트 흐름이라고 하며, 이벤트가 흐르는 방식은 다음과 같이 2가지가 있다.
버블링이란 이벤트가 하위 요소에서 상위 요소 방향으로 전파되는 것이다.
캡처링이란 이벤트가 상위 요소에서 하위 요소 방향으로 전파되는 것이다.
이벤트 위임
이벤트 위임이란 하나의 부모에 이벤트를 등록하여 부모가 이벤트를 위임하는 방식이다.
하위 요소가 많아질수록 이벤트 리스너를 등록하는게 성능 저하의 원인이 되며 유지보수에도 부적합한 코드를 생산하게 한다.
// html
// js
document.querySelector("#parent-list").addEventListener("click", function (e) {
if (e.target && e.target.nodeName == "LI") {
console.log(List item ${e.target.id} was clicked!
);
}
});