📝

13. 이벤트

 

13-1. 이벤트

이벤트 작동 방식의 이해를 돕기 위해서 예제 코드를 첨부하므로 실행해보기를 바란다.

13-1-1. 이벤트(event)란?

이벤트는 시스템에서 발생하는 작업 또는 사건을 말한다. 특정 버튼을 클릭하거나 DOM 로드가 완료되는 등의 이벤가 발생하면 브라우저는 이와 같은 이벤트를 감지할 수 있다. 시스템은 이벤트가 발생할 때 일종의 신호를 생성하고 작성된 코드를 실행시킨다. 예를 들어 횡단보도에서 신호등이 빨간 불에서 초록 불로 바뀌고 이 신호가 보행자들에게 전달되면 신호를 전달받은 보행자들은 안전하게 횡단보도를  건널 수 있다. 마찬가지로 사용자가 키보드의 키를 눌렀을 때 함수를 호출해서 어떤 로직을 처리하고 싶다고 가정하자. 이 때, 브라우저는 사용자의 키보드 누름을 감지하고 이벤트를 발생시킬 수 있다. 그래서 이 특정 키를 누른 이벤트가 발생하면 특정 함수를 호출하도록 브라우저에 위임할 수 있다.
간단한 예시로 button 이 눌렸을 때, 배경이 임의의 색상으로 변경되는 코드를 보자.
 
<button>색깔 바꾸기</button>
 
JavaScript 코드는 다음과 같다.
 
const button = document.querySelector('button'); function randomNumber(num) { return Math.floor(Math.random() * (num+1)); } button.addEventListener('click', () => { const randomColor = `rgb(${randomNumber(255)}, ${randomNumber(255)}, ${randomNumber(255)})`; document.body.style.backgroundColor = randomColor; });
 
<button>요소에는 사용자가 버튼을 클릭할 때 발생하는 이벤트가 있다. 나중에 설명하겠지만 이벤트를 등록하는 메서드 addEventListener() 를 통해 이벤트 이름과 이벤트를 처리하는 함수가 전달된다. 그래서 사용자는 버튼의 addEventListener() 메서드를 호출하여 'click' 이벤트와 이벤트가 발생했을 때 호출하는 함수인 임의의 RGB 색상을 생성하는 함수를 전달해서 버튼이 눌릴 때마다 해당 이벤트를 감지하여 함수를 호출한다.

13-1-2.이벤트 타입

이벤트 타입은 이벤트의 종류를 나타내는 문자열이다. 웹 브라우저에는 약 200여 가지의 이벤트 종류가 있고 이벤트를 모두 설명할 수 없다. 모든 이벤트와 이벤트 객체의 속성을 공부할 수 없음으로 사용 빈도가 높은 이벤트 정도만 알아두자. 아래 설명되는 이벤트 캡처링과 버블링은 13-4 챕터에서 설명한다.

13-1-2-1. 마우스 이벤트

이벤트
설명
click
해당 요소를 클릭했을 때 실행된다.
dbclick
해당 요소에서 마우스 버튼을 더블 클릭했을 때 실행된다.
mousedown
해당 요소에서 마우스 버튼을 눌렀을 때 실행된다.
mouseup
해당 요소에서 눌렀던 마우스 버튼을 떼었을 때 실행된다.
mousemove
해당 요소에서 마우스를 움직였을 때 실행된다.
mouseover
마우스 커서를 HTML 요소 바깥에서 안으로 이동했을 때 실행된다. (버블링이 발생한다.)
mouseenter
마우스 커서를 HTML 요소 바깥에서 안으로 이동했을 때 실행된다. (버블링이 발생하지 않는다.)
mouseout
마우스 커서를 HTML 요소 안에서 바깥으로 이동했을 때 실행된다. (버블링이 발생한다.)
mouseleave
마우스 커서를 HTML 요소 안에서 바깥으로 이동했을 때 실행된다. (버블링이 발생하지 않는다.)
contextmenu
마우스 오른쪽 버튼을 눌렀을 때 실행된다.
 
어떤 elementclick 이벤트가 발생하면 동시에 mousedown, mouseup 이벤트도 발생한다. 사용자가 마우스를 누르면 mousedown 이벤트가 발생하고 마우스를 떼면 mouseup 이벤트가 발생하고 동시에 click 이벤트도 발생한다.
아래는 mousedown, mouseup의 예제이다.
 
<div class='box'></div> <input class='btn_clear' type='button' value='clear'> <div class='result'></div>
 
.box { background: yellow; height: 200px; width: 200px; }
 
밑에는 JavaScript 코드이다.
 
const div = document.querySelector('.box'); const btnClear = document.querySelector('.btn_clear'); const result = document.querySelector('.result'); div.addEventListener('click', (event) => { result.innerHTML+= '<div>click</div>'; }); div.addEventListener('mousedown', (event) => { result.innerHTML+= '<div>mousedown</div>'; }); div.addEventListener('mouseup', (event) => { result.innerHTML+= '<div>mouseup</div>'; }); btnClear.addEventListener('click', (event) => { result.innerHTML= ''; });
 
위 예제의 노란색 <div> 안에는 click, mousedown, mouseup 이벤트가 적용되어 있다. 여기서 노란색 <div>를 클릭하면 mousedown, mouseup, click 이벤트가 순서대로 발생하는 것을 확인할 수 있다. 노란색 <div> 안에서 마우스를 누르고, 마우스를 움직여서 노란색 <div> 밖에서 마우스를 떼면 mouseup, click 이벤트는 발생하지 않고 mousedown 이벤트만 발생한다. 반대로 노란색 <div> 밖에서 마우스를 누르고 마우스를 움직여서 노란색 <div> 안에서 마우스를 떼면 mousedown, click 이벤트는 발생하지 않고 mouseup 이벤트만 발생한다. 예제코드로 실행해보며 이해하는 것이 빠르니 상단에 있는 GitHub 링크에 접속해 예제코드를 꼭 실행해보기 바란다.
 

13-1-2-2. 키보드 이벤트

이벤트
설명
keydown
키를 눌렀을 때 실행되며 누르고 있을 때, 입력될 때도 실행된다.
keyup
키보드의 키를 눌렀다가 떼었을 때 실행된다.
keypress
키를 눌렀을 때 실행된다. 예전에는 사용되었으나, 브라우저에 따라 아시아권의 문자를 처리하지 못해서 더 이상 사용되지 않는다.
아래의 코드는 inputkeydown, keyup 이벤트를 등록하고 해당 이벤트가 발생하면 화면에 해당 이벤트 이름을 출력한다.
 
<input class='input' type='text'> <input class='clear' type='button' value='clear'> <div class='result'></div>
 
밑에는 JavaScript 코드이다.
 
const inputBox = document.querySelector('.input'); const btnClear = document.querySelector('.clear'); const result = document.querySelector('.result'); inputBox.addEventListener('keydown', () => { result.innerHTML += '<div>keydown</div>'; }); inputBox.addEventListener('keyup', () => { result.innerHTML += '<div>keyup</div>'; }); btnClear.addEventListener('click', () => { result.innerHTML = ''; });
 

13-1-2-3. 포커스 이벤트

이벤트
설명
focus
HTML 요소가 포커스를 받았을 때 실행된다. (버블링되지 않는다)
blur
HTML 요소가 포커스를 잃었을 때 실행된다. (버블링되지 않는다)
focusin
HTML 요소가 포커스를 받았을 때 실행된다. (버블링된다)
focusout
HTML 요소가 포커스를 잃었을 때 실행된다. (버블링된다)
focusin, focusout 는 뒤에서 설명할 이벤트 핸들러 등록 방식 중 addeventlistener() 방식으로 이벤트 등록해줘야 브라우저 상관없이 정상 작동한다.
아래의 코드는 inputblur 되었을 때, 올바른 이메일 형식인지 체크하는 경고 문구를 띄우고, focus 되었을 때, 사용자가 올바른 이메일 형식을 입력하도록 에러 메세지를 지우는 함수를 호출하는 예제이다.
 
아이디: <input type="email" id="input"> <div id="error"></div>
 
밑에는 JavaScript 코드이다.
 
input.onblur = function() { if (!input.value.includes('@')) { // @ 유무를 이용해 유효한 이메일 주소인지 확인 input.classList.add('invalid'); error.innerHTML = '이메일 형식이 올바르지 않습니다.'; } } input.onfocus = function() { if (this.classList.contains('invalid')) { // 유저가 새로운 값을 입력하기위해 경고 문구를 지움 this.classList.remove('invalid'); error.innerHTML = ""; } }

13-2. 이벤트 핸들러

13-2-1. 이벤트 핸들러

이벤트 핸들러(Event Handler)는 이벤트 발생 시 호출될 함수를 말한다. 예를 들어, 요소에 마우스를 올렸을 때 함수를 호출해 특정 동작을 처리한다고 가정해보자. 사용자가 요소에 마우스를 올리면 브라우저는 이 동작을 감지하여 마우스 이벤트를 발생시킬 수 있다. 마우스 이벤트 발생 시 사용자가 작성해놓은 크기 변경 함수를 호출할 경우, 해당 함수를 이벤트 핸들러라고 한다.

13-2-2. 이벤트 핸들러 등록

요소를 클릭했을 때 글자의 크기가 변하는 함수를 호출하고 싶다고 해보자. 이때 요소를 클릭하는 것은 사용자로 하여금 발생하는 동작이다. 사용자는 요소를 1초 후에 클릭할 수도 있고, 2시간 후에 클릭할 수도 있다. 따라서 개발자가 사용자의 요소 클릭이 언제 발생하는지 예측해서 해당 함수를 호출하기는 어렵다.
브라우저를 통해 이러한 어려움을 극복할 수 있다. 브라우저는 요소 클릭을 감지해서 클릭 이벤트를 발생시킬 수 있고, 이벤트 발생 시 이벤트 핸들러를 호출시키는 것을 브라우저에게 위임할 수 있다. 이렇게 이벤트 핸들러의 호출을 브라우저에게 위임하는 것을 이벤트 핸들러 등록이라고 한다. 이벤트 핸들러 등록은 3가지 방식을 통해 가능하다.

13-2-2-1. 이벤트 핸들러 속성 방식

HTML의 각 요소는 다양한 속성들을 가지고 있다. 그 중에서도 이벤트에 대응할 수 있는 속성들은 on 접두사와 함께 이벤트의 종류를 나타내는 이름으로 사용된다. 예를 들어, onclick 속성은 사용자의 클릭 이벤트에 대응할 수 있는 이벤트 핸들러 속성이다. 이벤트 핸들러 속성을 사용하여 그 값으로 함수 호출문과 같은 문(state)을 할당시켜 이벤트 핸들러를 등록한다.
h1 요소에 마우스를 올렸을 때 특정 문자를 콘솔에 출력해보자. 이벤트 핸들러 속성 방식을 이용해서 이벤트 핸들러를 등록한다면, 마우스 커서가 요소 안으로 들어가는 이벤트에 대응할 수 있는 onmouseover 속성을 사용하면 된다. ‘크게'라는 문자열을 인수로 가지는 handleSize 함수를 해당 이벤트 핸들러 속성의 값으로 할당시킨다.
 
<h1 onmouseover="handleSize('크게')">이벤트 핸들러를 배워봅시다.</h1>
HTML code
function handleSize(크기) { console.log(`글자가 ${크기} 변경되었습니다.`); }
JavaScript code
 
이벤트 핸들러 속성 방식을 사용하여 그 값으로 함수 호출문을 할당시켰기 때문에 인수를 전달할 수 있다. 위의 예시에서 ‘크게'라는 문자열이 인수로 전달되었다. 해당 코드를 실행하여 이벤트 핸들러가 등록된 요소(h1)에 마우스를 올리면, 콘솔에 ‘글자가 크게 변경되었습니다.' 라는 문자열이 출력된다. 브라우저가 마우스 이벤트를 감지하고 이벤트 핸들러(handleSize(크기))의 호출을 위임받아 함수를 실행했기 때문이다.
이벤트 핸들러 속성 방식은 한 가지 이상의 문을 값으로 할당시킬 수 있다. 아래의 코드처럼 하나의 문이 아닌 두 개의 문을 onclick 속성의 값으로 할당해보자. 이벤트 핸들러가 등록된 요소(p)를 클릭하면 콘솔에 ‘클릭’, ‘했죠?’ 라는 문자열이 출력된다. 이를 통해 이벤트 핸들러 속성 방식으로 여러 개의 문을 값으로 할당시킬 수 있음을 알 수 있다.
 
<p onclick="console.log('클릭'); console.log('했죠?');">안녕하세요.</p>
HTML code

13-2-2-2. 이벤트 핸들러 프로퍼티 방식

DOM node 객체는 이벤트에 대응할 수 있는 이벤트 핸들러 프로퍼티를 가진다. 이전의 이벤트 핸들러 속성 이름과 마찬가지로 on 접두사와 함께 이벤트의 종류를 나타내는 이름이 프로퍼티의 키가 된다.
이벤트 핸들러 프로퍼티 방식으로 이벤트 핸들러를 등록하려면 이벤트 타깃(Event Target)과 이벤트 타입(Event Type) 그리고 이벤트 핸들러 함수를 지정해야 한다. h1 요소를 클릭하면 콘솔에 문자열을 출력하는 함수를 호출하는 코드를 살펴보자.
 
<h1>제목입니다.</h1>
HTML code
const h1 = document.querySelector('h1'); h1.onclick = function () { console.log('클릭했습니다.'); };
JavaScript code
 
이벤트를 발생시킬 이벤트 타깃은 h1 요소이다. 요소가 ‘클릭'되었을 때 함수를 실행시키기 위해 이벤트 타입은 onclick 으로 작성하며 이것이 프로퍼티의 키가 된다. 이벤트 핸들러는 콘솔에 ‘클릭했습니다' 라는 문자열을 출력하는 함수이다. 이처럼 이벤트 핸들러 프로퍼티를 이용해서 이벤트 핸들러 함수를 바인딩 시키는 것이 이벤트 핸들러 프로퍼티 방식이다.
 
<h1>제목입니다.</h1>
HTML code
const h1 = document.querySelector('h1'); h1.onclick = function () { console.log('클릭했습니다 1'); }; h1.onclick = function () { console.log('클릭했습니다 2'); };
JavaScript code
 
주의할 점은 이벤트 핸들러 속성 방식과 다르게 하나의 이벤트 핸들러만 바인딩 시킬 수 있다는 부분이다. 위 코드를 사용해서 이벤트 핸들러 프로퍼티 방식으로 하나의 이벤트에 두 개의 함수를 바인딩 시켜보자. 이때 이벤트 타깃인 h1 요소를 클릭하면 ‘클릭했습니다 2’ 라는 문자열이 콘솔에 출력된다. 가장 먼저 바인딩된 이벤트 핸들러는 그 다음 바인딩된 이벤트 핸들러로 재할당되어 실행되지 않는 것을 볼 수 있다.

13-2-2-3. addEventListener 메서드 방식

addEventListener 메서드 방식은 3개의 매개변수를 가진다. 첫 번째 매개변수는 이벤트 타입이다. 이전의 방식들과 다르게 on 접두사를 사용하지 않는다. 두 번째 매개변수는 이벤트 핸들러다. 마지막 매개변수는 이벤트 전파 단계이다. 기본값이 bubbling이므로 생략하면 버블링 단계에서 이벤트를 캐치한다. true 값을 지정할 경우 캡쳐링 단계에서, false 값을 지정할 경우 버블링 단계에서 이벤트를 캐치하게 된다. 버블링과 캡쳐링에 대해서는 13-4. 이벤트 전파에서 자세하게 알아보겠다.
h1 요소를 클릭하면 콘솔에 문자열을 출력하는 함수를 실행하는 예시를 이벤트 핸들러 프로퍼티 방식에서 확인했다. 똑같은 예시를 addEventListener 메서드 방식을 사용해서 작성해보고 차이점을 알아보자.
 
<h1>제목입니다.</h1>
HTML code
const h1 = document.querySelector('h1'); h1.addEventListener('click',function () { console.log('클릭했습니다.'); }); // addEventListener(이벤트 타입, 이벤트 핸들러, 이벤트 전파단계)
JavaScript code
 
이벤트 핸들러 프로퍼티 방식은 이벤트 핸들러를 프로퍼티에 바인딩한다. 하지만, addEventListener 메서드 방식은 이벤트 핸들러를 인수로 전달하고 있다. 또한, 이벤트 핸들러 프로퍼티와는 다르게 동일한 요소에서 발생한 이벤트에 대해 여러 이벤트 핸들러를 등록할 수 있다는 차이점이 있다.
 
<button>클릭하세요!</button>
HTML code
const btn = document.querySelector('button'); btn.addEventListener('click',function () { console.log('클릭했어요! 1'); }); btn.addEventListener('click', function () { console.log('클릭했어요! 2'); });
JavaScript code
 
해당 코드에서 동일한 button 요소에 대해 동일한 click 이벤트가 발생했을 때, 두 개의 이벤트 핸들러가 등록되어 있다. 버튼을 누르면 콘솔에 ‘클릭했어요! 1’ 과 ‘클릭했어요! 2’ 라는 문자열이 순서대로 출력된다. 즉, addEventListener 메서드는 여러 개의 이벤트 핸들러를 등록할 수 있으며 등록된 순서대로 호출된다는 것을 알 수 있다.

13-2-3. 이벤트 핸들러 제거

addEventListener 메서드로 등록한 이벤트 핸들러는 removeEventListener 메서드를 사용해 제거할 수 있다. 주의할 점은 addEventListener 메서드에 전달한 인수와 동일한 인수를 가지고 removeEventListener 메서드를 사용해야 한다는 것이다.
 
<button>클릭하세요!</button>
HTML code
const btn = document.querySelector('button'); const handleClick = function () { console.log('클릭!'); } btn.addEventListener('click', handleClick); // addEventListener 메서드를 이용하여 이벤트 핸들러를 등록한다. btn.removeEventListener('click', handleClick); // removeEventListener 메서드를 이용하여 이벤트 핸들러를 제거한다. btn.removeEventListener('click', handleClick, true); // addEventListener 메서드에 전달한 인수와 다른 인수를 사용해서 이벤트 핸들러가 제거되지 않는다.
JavaScript code
 
버튼을 클릭하면 콘솔에 문자열이 출력되는 이벤트 핸들러를 등록했는데 제거하고 싶다고 가정해보자. 등록된 이벤트 핸들러 제거를 위해 addEventListener 메서드에 전달한 인수와 동일한 인수를 removeEventListener 메소드에 전달하여 이벤트 핸들러가 정상적으로 제거된다. 하지만 세번째 매개변수에 true 값을 주면 전달한 인수와 동일하지 않은 인수가 되므로 이벤트 핸들러가 제거되지 않는다.

13-3. 이벤트 객체

JavaScript에서는 이벤트가 발생할 경우 이벤트 객체를 생성하여 이벤트에 대한 정보를 이벤트 핸들러 함수에 넘겨준다. 이 파트를 통해서 이벤트 객체에 대하여 학습해 보자.

13-3-1. 이벤트와 이벤트 객체

자바스크립트에서 이벤트는 객체 형식으로 정보가 저장되어 이벤트가 타깃으로 하는 객체(DOM, Document, Window 등)로 보내진다. 이때 이벤트의 정보를 저장하는 객체를 이벤트 객체라고 하며 이벤트가 타깃으로 하는 객체의 이벤트 핸들러 인자로 이벤트 객체를 받아와 발생한 이벤트에 대해 좀 더 효과적인 대응을 할 수 있다.
 
💡
이벤트가 타깃으로 하는 객체[1] 이벤트가 타깃으로 하는 객체란 EventTarget 인터페이스[2]를 따르는 객체로 이벤트를 수신할 수 있고 수신한 이벤트에 대해 이벤트 핸들러를 가질 수 있는 객체를 말한다. 대표적인 예로는 Element, Document, Window가 있다.
 
다음의 코드를 통해 이벤트가 발생했을 때 이벤트 핸들러 함수의 인자로 이벤트 객체를 받아오는 것을 확인할 수 있다.
 
<button>클릭!</button>
HTML code
const btn = document.querySelector("button"); btn.onclick = function (event) { console.log("클릭되었습니다."); console.log(event); }
JavaScript code
 
위의 코드는 아래의 순서대로 동작한다.
  1. 마우스로 버튼을 클릭하면 브라우저는 클릭을 감지한다.
  1. 이에 따라 발생한 클릭 이벤트의 정보를 담은 이벤트 객체가 생성되고 생성된 객체는 13-4. 이벤트 전파에서 설명할 원리에 따라 클릭이 발생한 요소(button)로 보내지게 된다.
  1. button으로 클릭 이벤트의 정보를 담은 이벤트 객체가 보내지면 button의 이벤트 핸들러가 클릭 이벤트가 발생했음을 인지하고 이벤트 핸들러 함수를 수행한다.
  1. 이벤트 핸들러 함수가 수행되는 동안 “클릭되었습니다”가 출력되고 event라는 이름으로 받은 이벤트 객체를 콘솔에 출력한다.
  1. 마우스로 버튼을 클릭하면 브라우저는 클릭을 감지한다.
  1. 이에 따라 발생한 클릭 이벤트의 정보를 담은 이벤트 객체가 생성되고 생성된 객체는 13-4. 이벤트 전파에서 설명할 원리에 따라 클릭이 발생한 요소(button)로 보내지게 된다.
  1. button으로 클릭 이벤트의 정보를 담은 이벤트 객체가 보내지면 button의 이벤트 핸들러가 클릭 이벤트가 발생 했음을 인지하고 이벤트 핸들러 함수를 수행한다.
  1. 이벤트 핸들러 함수가 수행되는 동안 “클릭되었습니다”가 출력되고 event 라는 이름으로 받은 이벤트 객체를 콘솔에 출력한다.
 
💡
이벤트 객체의 이름[3] 이벤트 핸들러는 넘겨 받은 이벤트 객체를 사용하기 위해서는 이벤트 핸들러 함수의 인자로 이벤트 객체를 넘겨 받을 이름을 명시해주어야 한다. 일반적으로는 event, evt, e와 같은 이름으로 명시해주고 원한다면 다른 이름으로 변경하여도 에러 없이 사용할 수 있다. 하지만, 인라인 방식에서 이벤트 객체를 인자로 받으려고 하는 경우 반드시 event라는 이름으로 인자를 명시해 주어야한다.[4]
 
// 인라인 방식으로 event 객체 받아오기 <button onclick="handleClick(event)">클릭!</button>
 
button의 이벤트 핸들러 함수 실행 결과button의 이벤트 핸들러 함수 실행 결과
button의 이벤트 핸들러 함수 실행 결과
 
출력을 확인해 보면 클릭 이벤트에 대한 결과로 이벤트 핸들러 함수에 의해 PointerEvent 객체가 출력되는 것을 확인할 수 있다. 이처럼 이벤트가 발생하면 이벤트 핸들러 함수에서 관련 이벤트 객체를 받아와 사용할 수 있다. 이벤트 객체가 어떻게 사용될 수 있는지는 13-3-5. 간단한 이벤트 객체 사용 예시를 통해서 자세히 알아보겠다.
이벤트와 이벤트 객체에 대한 개념을 처음 접하였다면 용어 사용에 있어 이벤트와 이벤트 객체를 혼동할 수 있는데 이벤트는 브라우저가 인식할 수 있는 어떠한 사건이라는 점, 이벤트 객체는 이벤트 발생을 브라우저가 인식해서 사건의 처리를 위해 넘겨주는 사건의 정보라는 점을 정리하고 넘어간다면 추후 학습에 혼란을 줄일 수 있을 것이다.
 
💡
이벤트와 이벤트 객체의 상관관계 브라우저에서 이벤트 객체를 넘겨받는 방식이 아닌 이벤트 객체 생성자 함수를 이용하여 이벤트 객체를 생성할 수도 있다. 이렇게 생성한 이벤트 객체를 사용하면 이벤트를 다시 발생시킬 수도 있다. 결국 이벤트가 발생하면 정보가 담긴 이벤트 객체가 생성될 수도 있고 이벤트 객체를 통해 다시 이벤트를 발생시킬 수도 있다는 말이다. 이 둘의 상관관계는 매우 밀접하며 공식 문서에서는 이벤트 객체는 이벤트라고 명명된다고 명시되어 있다.
 
An Event object is simply named an event. 출처: https://dom.spec.whatwg.org/#event
 

13-3-2. 이벤트 객체의 다양한 종류

이벤트 객체는 이벤트의 정보를 담고 있는 객체라고 앞서 설명하였다. 다양한 타입의 이벤트를 표현하기 위해서는 이벤트 객체 또한 다양한 종류의 이벤트 객체를 가져야 한다. 다음 예제를 통해 이벤트 타입에 따라 다른 이벤트 객체가 생성됨을 확인해 보자.
 
<button>클릭!</button>
HTML code
const btn = document.querySelector("button"); btn.onmousedown = function (event) { console.log(event); } btn.onmouseup = function (event) { console.log(event); } btn.onclick = function (event) { console.log("클릭되었습니다."); console.log(event); }
JavaScript code
 
출력 결과를 확인해 보면 mousedown, mouseup 이벤트가 발생했을 때는 MouseEvent 이벤트 객체가 생성되었고 click 이벤트가 발생했을 때는 PointerEvent 이벤트 객체가 생성된 것을 확인할 수 있다.
 
button의 이벤트 핸들러 함수 실행 결과button의 이벤트 핸들러 함수 실행 결과
button의 이벤트 핸들러 함수 실행 결과
 
우리는 이를 통해 다음과 같은 사실을 정리할 수 있다.
 
  1. 이벤트 객체의 종류는 이벤트 타입에 따라 달라진다.
  1. 유사한(서로 관계가 있는) 이벤트들은 같은 이벤트 객체를 이용하여 이벤트 정보를 나타낼 수 있다.
 
이벤트의 타입의 종류는 너무나 많고 그에 따른 이벤트 객체의 종류도 다양하다. 또한 유사한 이벤트의 경우 같은 이벤트 객체를 가지지도 하고 다른 이벤트 객체를 가지기도 하듯 이벤트 타입과 이벤트 객체 사이에는 다양한 관계가 존재한다. 이러한 세부적인 내용을 알아보기엔 내용이 너무 방대하기 때문에 자세한 내용에 관해서 아래의 링크를 통해 확인해 보도록 하자.
 
💡
MDN 문서 : 이벤트 객체의 종류 MDN 문서에서는 각 이벤트 객체가 의미하는 것, 상속 구조, 각각의 프로퍼티, 메서드 그리고 이 이벤트 객체가 어떤 타입의 이벤트가 발생했을 때 생기는지에 관한 설명해 주고 있다. MDN 문서 링크 : https://developer.mozilla.org/en-US/docs/Web/API/Event#interfaces_based_on_event
 
우리는 저러한 세부적인 내용보다는 이벤트 객체가 어떤 내용을 가지고 있고 다양한 이벤트 객체를 생성하기 위해 어떤 방법을 사용했는지에 대해 공부해 보도록 하겠다.
 

13-3-3. 이벤트 객체 구조

JavaScript에서는 Event 인터페이스를 규정하여 모든 이벤트 객체의 공통된 속성을 정의하고 이 내용을 상속 및 확장한 파생 인터페이스를 통해 다양한 종류의 이벤트 객체의 속성을 정의한다.[5] 이에 따라 이벤트가 발생하게 되면 발생한 이벤트의 종류에 따라 특정 이벤트 인터페이스를 따르는 이벤트 객체가 생성되게 된다.
 
💡
인터페이스 JavaScript ES6에서는 인터페이스를 다음과 설명합니다.
인터페이스는 관련 값이 특정 규격과 일치하는 속성 키 집합이다. 출처: https://262.ecma-international.org/6.0/#sec-iteration
 
어떠한 객체가 인터페이스를 따른다는 말은 인터페이스에서 규정하는 모든 속성을 인터페이스를 따르는 객체에서 제공한다는 뜻이다. JavaScript에서는 다른 언어처럼 사용자가 인터페이스를 정의할 수도 인터페이스를 따르는 객체를 생성할 수 없다. 그저 덕 타이핑[6]과 같이 규정을 만들고 그 규정에 해당하는 모든 속성을 제공하면 특정 인터페이스를 기반으로 한다, 특정 인터페이스를 따른다라고 한다.
Event 인터페이스에서 규정하는 이벤트 객체의 공통된 속성과 메서드는 다음과 같다.
 
Event 인터페이스 속성 규정[9]
속성
설명
Event.bubbles
이벤트가 DOM을 타고 버블링되는지 나타낸다.
Event.cancelable
이벤트를 취소할 수 있는지 나타낸다.
Event.composed
이벤트가 shadow DOM과 일반 DOM의 경계를 넘어 버블링할 수 있는지 나타내는 불리언이다.
Event.currentTarget
이벤트의 현재 대상, 즉 이벤트의 전파 과정 중 이벤트가 현재 위치한 객체를 가리킨다. '리타겟팅'(retargeting)으로 인해 중간에 값이 바뀌었을 수 있다.
Event.defaultPrevented
이벤트의 event.preventDefault() 호출이 이벤트를 취소했는지 나타낸다.
Event.eventPhase
이벤트 흐름에서 현재 처리 중인 단계를 나타낸다. NONECAPTURIING_PHASE,  AT_TARGETBUBBLING_PHASE 중 하나이다.
Event.isTrusted
이벤트가 (사용자 클릭 등으로 인해) 브라우저에 의해 초기화됐는지, (생성자 등) 스크립트에 의해 초기화됐는지 나타낸다.
Event.target
이벤트가 처음에 발생한 대상을 가리킨다.
Event.timeStamp
이벤트가 생성된 시간(밀리초)이다. 과거에는 명세 상 UNIX 시간과 이벤트 생성 시점까지의 차이였지만 실제 구현은 브라우저마다 달랐다. 지금은 DOMHighResTimeStamp를 반환합니다.
Event.type
이벤트의 이름을 나타낸다, 대소문자를 구분하지 않는다.
 
Event 인터페이스 메서드 규정[9]
메서드
설명
Event.composedPath()
이벤트의 경로, 즉 수신기가 발동했거나 발동할 모든 객체의 배열을 반환한다. 섀도 루트의 ShadowRoot.mode가 closed인 경우 섀도 트리의 노드는 포함하지 않습니다.
Event.preventDefault()
취소 가능한 경우 이벤트를 취소한다.
Event.stopImmediatePropagation()
이 특정 이벤트에 대해서 다른 모든 수신기의 호출을 방지한다. 같은 요소에 부착된 수신기는 물론 캡처링과 버블링 단계에서 후속으로 호출할 수신기의 발동도 막는다.
Event.stopPropagation()
이벤트의 DOM 내 추가 전파를 방지한다.
 
Event 인터페이스의 규정을 따르는 이벤트 객체를 생성하기 위해서는 Event() 생성자 함수를 통해 이벤트 객체를 생성할 수 있다. Event() 생성자 함수의 사용법은 다음과 같다.
 
// click 타입 이벤트 객체를 생성, Event()의 인자로 event의 타입을 넘겨준다. const eventObject = new Event("click"); console.log(eventObject);
 
notion imagenotion image
 
Event 인터페이스뿐만 아니라 이들을 상속, 확장하는 파생 인터페이스들도 이벤트 객체를 생성하기 위해 각자의 생성자 함수를 가진다. 자세한 내용은 13-3-2. 이벤트 객체의 다양한 종류의 MDN 문서 링크를 확인하기 바란다.
 

13-3-4. 파생 인터페이스와 상속 구조

JavaScript에서는 Event 인터페이스는 모든 이벤트 객체의 공통된 속성으로만 정의하고 이벤트마다 필요한 추가적인 내용은 Event 인터페이스를 상속 및 확장한 파생 인터페이스를 규정하여 각 이벤트에 맞는 이벤트 객체를 생성한다. 상속 및 확장은 Event 인터페이스와 파생 인터페이스뿐만 아니라 파생 인터페이스 사이에도 일어난다. 상속 및 확장 관계를 가지는 인터페이스들을 좀 더 살펴보면 상속받는 인터페이스의 생성자 함수는 [[Prototype]]으로 상속하는 인터페이스의 생성자 함수를 가리키는 것을 확인할 수 있다.
13-3-2. 이벤트 객체의 다양한 종류의 예제의 출력을 다시 확인해보자. 이벤트 핸들러를 통해 출력된 이벤트 객체 MouseEvent와 PointerEvent를 펼쳐서 prototype을 확인해 보면 다음과 같은 상속 관계를 확인할 수 있다.
 
13-3-2. 이벤트 객체의 다양한 종류의 예제 출력13-3-2. 이벤트 객체의 다양한 종류의 예제 출력
13-3-2. 이벤트 객체의 다양한 종류의 예제 출력
 
PoineterEvent prototype 간략화
출처: https://developer.mozilla.org/en-US/docs/Web/API/PointerEventPoineterEvent prototype 간략화
출처: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
PoineterEvent prototype 간략화 출처: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
 
Event 인터페이스를 통해서 공통된 속성이 상속되며 PointerEvent는 Event 인터페이스를 상속, 확장한 파생 인터페이스 UIEvent와 MouseEvent를 상속하여 이 둘의 속성 또한 가질 수 있다. 이처럼 JavaScript에선 이벤트 객체를 표현함에 있어서 공통된 속성을 나타내기 위해 특정 인터페이스로 묶고 공통된 속성 이외의 속성들을 나타내기 위해 확장을 이용하여 하위 인터페이스를 정의하는 방식을 사용한다.
위 상속 관계를 그림으로 나타내면 다음과 같다.
 
알잘딱깔센 javascript : 이벤트 객체 구조알잘딱깔센 javascript : 이벤트 객체 구조
알잘딱깔센 javascript : 이벤트 객체 구조
 

13-3-5. 간단한 이벤트 객체 사용 예시

이때까지 우리는 이벤트 객체라는 것이 생성되고 이벤트 객체는 어떻게 구성되었는지 확인해보았다.앞선 내용을 정리해 보자면 다음과 같다.
 
이벤트 객체 내용 정리
  • 이벤트가 발생하면 관련된 정보를 전달하기 위해 이벤트 객체를 생성한다.
  • 생성된 이벤트 객체는 관련 이벤트를 처리하는 이벤트 핸들러 함수의 인자로 전달되어 효율적인 이벤트 처리를 돕는다.
  • 다양한 이벤트의 종류가 있는 것처럼 다양한 종류의 이벤트 객체가 생성된다.
  • 생성되는 모든 이벤트 객체는 Event 인터페이스라는 규정을 따른다.
  • Event 인터페이스만으로는 다양한 이벤트에 필요한 모든 정보를 담을 수 없기 때문에 Event 인터페이스를 상속하는 파생 인터페이스를 통해 필요한 더 많은 정보를 담은 이벤트 객체를 생성한다.
  • Event 인터페이스뿐만 아니라 파생 인터페이스 사이에도 공통된 기능들을 효율적으로 코드로 구현하기 위해 상속 구조로 각각의 인터페이스들이 연결된다.
 
이제 이러한 이벤트 객체를 이용하여 어떤 것을 할 수 있는지 알아보자.
 

13-3-5-1. 마우스 체이서 만들기

이벤트 객체는 발생한 이벤트에 대해 효율적인 처리를 위해 사용될 수 있다고 하였다. 다음의 예시를 살펴보자.
 
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mouse Animation</title> <style> * { margin: 0; } body { background-color: #3d5a80; } .circle { position: absolute; top: 0; left: 0; width: 150px; height: 150px; border-radius: 50%; background-color: #ee6c4d; transform: translate(-50%, -50%); } </style> </head> <body> <div class="circle"></div> <!-- JavaScript 코드 --> <script> </script> </body> </html>
HTML code
const circle = document.querySelector(".circle"); document.addEventListener("mousemove", (e) => { const mouseX = e.clientX; const mouseY = e.clientY; circle.style.left = mouseX + 'px'; circle.style.top = mouseY + 'px'; });
JavaScript code
 
위 코드를 실행하면 마우스 커서를 따라다니는 원(마우스 체이서)이 만들어진다. document에서 mousemove 이벤트를 감지하고 이벤트가 발생할 때마다 이벤트 객체에서 마우스 포인터의 위치 정보를 받아올 수 있다. 이 위치 정보를 css style에 적용하여 따라다니는 원을 만들어 낼 수 있게 되는 것이다. 아래의 사이트에서 코드 구현 결과를 확인할 수 있다.
 
 
이처럼 이벤트 객체를 통해 발생한 이벤트에 대한 정보를 받아올 수 있고 이로써 이벤트 핸들러는 이벤트 처리 즉 이벤트가 발생했을 때 수행해야 하는 작업에 있어서 필요한 정보를 얻어 이벤트 효과적으로 대응할 수 있다.

13-4. 이벤트 전파

이벤트 전파, 위임의 이해를 돕기 위해 아래 예제 코드를 첨부한다.
 
브라우저에서 이벤트가 발생하면 브러우저에 의해 호출되는 함수인 이벤트 핸들러 또는 이벤트 리스너가 동작하면서 캡처링과 버블링이 발생한다. 이벤트 객체가 전파되는 방향에 따라 이벤트 전파를 3단계로 구분한다. 첫 번째인 캡처링 단계는 이벤트가 상위 요소에서 하위 요소 방향으로 전파하는 것이고 타깃 단계는 이벤트가 이벤트 타깃에 도달하는 것이다. 마지막으로 버블링 단계는 이벤트가 하위 요소에서 상위 요소 방향으로 전파하는 것을 말한다.
 
알잘딱깔센 JavaScript알잘딱깔센 JavaScript
알잘딱깔센 JavaScript
 
💡
eventPhase란? eventPhase는 읽기 전용 속성으로 현재 평가 중인 이벤트 흐름 단계를 나타낸다.
  1. capturing : 1번
  1. target : 2번
  1. bubbling : 3번
 
예제로 이벤트 전파 단계를 확인해보자.
 
<ul id="flowers"> <li id="rose">Rose</li> <li id="freesia">Freesia</li> <li id="carnation">Carnation</li> </ul>
HTML code
const flowers = document.getElementById('flowers'); const rose = document.getElementById('rose'); // li 요소를 클릭한 경우 flowers.addEventListener('click', event =>{ console.log(`${event.eventPhase}`); // 1 캡처링 단계 console.log(`${event.target}`); // [object HTMLLIElement] console.log(`${event.currentTarget}`); // [object HTMLUListElement] }, true); rose.addEventListener('click', event =>{ console.log(`${event.eventPhase}`); // 2 타깃 단계 console.log(`${event.target}`); // [object HTMLLIElement] console.log(`${event.currentTarget}`); // [object HTMLUListElement] }); flowers.addEventListener('click', event =>{ console.log(`${event.eventPhase}`); // 3 버블링 단계 console.log(`${event.target}`); // [object HTMLLIElement] console.log(`${event.currentTarget}`); // [object HTMLUListElement] });
JavaScript code
 
예제를 확인해보면 li 요소를 클릭해 이벤트가 발생했기 때문에 li 요소가 이벤트 타깃(event.target)이고 ul 요소는 커런트 타깃(event.currentTarget)이다. 이벤트의 흐름은 앞서 설명했듯이 캡처링이 먼저 실행된다. 이후 rose 클릭 시 이벤트 타깃과 이벤트 핸들러가 바인딩된 커런트 타깃이 동일한 li 요소이므로 타깃 단계의 이벤트가 실행된다. 마지막으로 버블링이 실행된다.
 
notion imagenotion image
 
💡
이벤트가 발생한 요소에 접근하고 싶을 때 target: 이벤트가 처음 발생했던 대상 DOM 요소의 참조를 갖는다. currenttarget: 발생한 이벤트가 등록된 DOM 요소의 참조를 갖는다.

13-4-1. 버블링

이벤트가 제일 깊은 곳에 있는 요소에서 시작해 부모 요소를 거슬러 올라가며 발생하는 모양이 마치 물속 거품(bubble)과 닮았다고 하여 정해진 이름이다. 최하위 요소부터 시작해 최상위 요소인 Window에 도달할 때까지 이벤트를 지속한다. 대부분의 이벤트는 버블링이 기본 동작이다.
 
<div class="box1"> box1 <div class="box2"> box2 <div class="box3"> box3 </div> </div> </div>
HTML code
const boxes = document.querySelectorAll('div'); boxes.forEach(function(div) { div.addEventListener('click', boxEvent); }); function boxEvent(event) { console.log(event.currentTarget.className); }
JavaScript code
 
notion imagenotion image
 
가장 하위 요소인 box3를 클릭할 경우 이벤트 리스너가 작동하면서 상위 요소인 부모 요소에 등록된 클릭 이벤트 리스너가 box3, box2, box1 순서대로 실행된다. box2를 클릭하는 경우에는 box2부터 box1까지 이벤트가 실행된다.
 
💡
버블링이 불가능한 이벤트 종류 포커스 이벤트 : focus / blur 리소스 이벤트 : load / unload / abort / error 마우스 이벤트 : mouseenter / mouseleave 버블링이 가능한 이벤트 종류 포커스 이벤트 : focusin / focusout 마우스 이벤트 : mouseover / mouseout
버블링이 불가능한 이벤트는 가능한 이벤트로 대체할 수 있다. 따라서 캡처링을 이용해 이벤트를 실행하는 경우는 흔하지 않다.

13-4-2. 캡처링

최상위 요소인 Window에서 시작해 DocumentHtmlElement를 거쳐 이벤트가 최하위 요소에 도달할 때까지 지속한다. 캡처링 단계에서 이벤트를 실행하고자 할 때는 addEventListener 세 번째 인수에 capture: true 값을 넣어준다. capture의 기본 값은 false 이므로 false를 전달하는 경우에는 타깃 단계와 버블링 단계의 이벤트만 실행한다.
 
<div class="box1"> box1 <div class="box2"> box2 <div class="box3"> box3 </div> </div> </div>
HTML code
const boxes = document.querySelectorAll('div'); boxes.forEach(function(div) { div.addEventListener('click', boxEvent, { capture: true // 캡처링을 동작하게 하는 옵션 }); }); function boxEvent(event) { console.log(event.currentTarget.className); }
JavaScript code
 
notion imagenotion image
 
버블링과 반대 방향으로 이벤트 리스너가 작동하면서 이벤트가 실행된다. box3를 클릭하면 상위 요소인 box1부터 box2, box3 순서대로 실행된다. box2를 클릭하는 경우에는 box1부터 box2까지 이벤트가 실행된다.

13-4-3. 버블링과 캡처링이 혼용되는 경우

이벤트 핸들러가 하나가 아니고 혼용하여 사용하는 경우에는 어떻게 이벤트가 호출되는지 알아보자.
 
<style> html, body { height: 100%;} </style> <body> body입니다. <div> div입니다. <p> p입니다. </p> </div> </body>
HTML code
// 버블링 단계 document.body.addEventListener('click', () => { console.log('Body element event handler'); }); // 캡처링 단계 document.querySelector('div').addEventListener('click', () => { console.log('Div element event handler'); }, true); // 타깃 단계 document.querySelector('p').addEventListener('click', () => { console.log('P element event handler'); });
JavaScript code
 
예제에는 버블링 단계와 캡처링 단계, 타깃 단계의 이벤트를 캐치하는 세 가지의 이벤트 리스너가 있다. 이벤트는 캡처링 단계, 타킷 단계, 버블링 단계 순으로 전파되므로 p 요소에서 클릭 이벤트가 발생하면 3번째 인수에 true 값을 준 캡처링 단계인 div 요소의 이벤트 핸들러가 호출된 후 타깃 단계인 p 요소의 이벤트 핸들러가 호출된다. 그 후 마지막으로 버블링 단계인 body 요소의 이벤트 핸들러가 호출된다.
 
notion imagenotion image

13-5. 이벤트 전파 방지 방법

13-5-1. preventDefault()

preventDefault 메서드는 이벤트 뿐만 아니라 요소의 기본 동작을 중지한다. a 요소를 클릭하면 href라는 a요소의 기본 속성에 따라 naver 홈페이지로 이동한다. 하지만 preventDefault 메서드를 호출해 a 요소의 기본 동작을 중지할 수 있다. 그러므로 NAVER 클릭시 아무런 동작도 발생하지 않는다.
 
<a href="https://www.naver.com">NAVER</a>
HTML code
document.querySelector('a').onclick = e => { event.preventDefault(); };
JavaScript code

13-5-2. stopPropagation()

stopPropagation 메서드는 이벤트가 버블링 단계와 캡처링 단계에서 전파되는 것을 방지한다. 전파를 방지해도 이벤트의 기본 동작은 실행되며stopPropagation 메서드는 동일한 이벤트 대상에서 다른 이벤트 리스너는 멈추지 않는다. stopPropagation 메서드가 이벤트 전파를 중지하면서 p 요소를 클릭시 p이벤트만 실행하고 상위 요소인 div 이벤트로 이벤트 전파가 일어나지 않는다.
 
<div id="div"> div입니다. <p id="p"> p입니다. <a href="https://www.naver.com" id="a">NAVER</a> </p> </div>
HTML code
const div = document.getElementById('div'); const p = document.getElementById('p'); const a = document.getElementById('a'); div.addEventListener('click', () => { console.log('div 클릭'); }); p.addEventListener('click', () => { event.stopPropagation(); // 이벤트 전파 중지 console.log('이벤트 전파 중지의 p 클릭'); }); p.addEventListener('click', () => { console.log('p 클릭'); }); a.addEventListener('click', () => { console.log('a 클릭'); });
JavaScript code
 
notion imagenotion image
 

13-5-3. stopImmediatePropagation()

stopPropagation 메서드는 동일한 이벤트 대상에 다른 이벤트 리스너는 멈추지 않는다. 하지만 stopImmediatePropagation 메서드는 다른 이벤트 리스너가 멈춘다. 이벤트 리스너 중 하나에서 stopImmediatePropagation 메서드를 호출하면 그 이후의 나머지 이벤트 리스너는 호출되지 않는다. p 요소를 클릭하면 stopImmediatePropagation 메서드를 호출했기 때문에 다른 이벤트 리스너는 멈춘다.
 
<div id="div"> div입니다. <p id="p"> p입니다. <a href="https://www.naver.com" id="a">NAVER</a> </p> </div>
HTML code
const div = document.getElementById('div'); const p = document.getElementById('p'); const a = document.getElementById('a'); div.addEventListener('click', () => { console.log('div 클릭'); }); p.addEventListener('click', () => { event.stopImmediatePropagation(); // 이벤트 전파 중지 console.log('이벤트 전파 중지의 p 클릭'); }); p.addEventListener('click', () => { console.log('p 클릭'); }); a.addEventListener('click', () => { console.log('a 클릭'); });
JavaScript code
 
notion imagenotion image

13-6. 이벤트 위임

이벤트 위임은 앞서 학습한 이벤트 전파를 활용해 상위 DOM 요소에 이벤트 핸들러를 등록하는 것이다. 하위 DOM 요소에 각각 여러 개의 이벤트 핸들러를 등록할 필요 없이 상위에만 이벤트 핸들러를 등록하면 코드가 간결해진다. 그럼 예제를 통해 이벤트 위임에 대해 알아보자.
 
<ul id="flowers"> <li id="rose" class="active">Rose</li> <li id="whiterose">whiterose</li> <li id="freesia">Freesia</li> <li id="carnation">Carnation</li> </ul> <div>선택한 꽃 : <b class="msg"></b></div>
HTML code
const flowers = document.getElementById('flowers'); const msg = document.querySelector('.msg'); function activate({target}) { [...flowers.children].forEach(flowers => { flowers.classList.toggle('active', flowers === target); msg.textContent = target.id; }); } document.getElementById('rose').onclick = activate; document.getElementById('freesia').onclick = activate; document.getElementById('carnation').onclick = activate;
JavaScript code
 
위 예제에서는 모든 li 요소에 이벤트 핸들러를 등록했다. 이벤트 위임을 사용해 코드를 수정해 보자.
 
<ul id="flowers"> <li id="rose" class="active">Rose</li> <li id="whiterose">whiterose</li> <li id="freesia">Freesia</li> <li id="carnation">Carnation</li> </ul> <div>선택한 꽃 : <b class="msg"></b></div>
HTML code
const flowers = document.getElementById('flowers'); const msg = document.querySelector('.msg'); function activate({target}) { if(!target.matches('#flowers > li')) return; [...flowers.children].forEach(flowers => { flowers.classList.toggle('active', flowers === target); msg.textContent = target.id; }); } flowers.onclick = activate;
JavaScript code
 
li 요소에 각각 이벤트 핸들러를 등록하지 않고 상위 요소인 ul 요소에 activate 이벤트 핸들러를 등록했다. 상위 요소에 이벤트 핸들러를 등록했기 때문에 다른 요소에 접근할 가능성이 있다. 그러므로 ul 요소의 li 요소가 아니라면 무시하는 코드를 넣어 주어야 한다. 이벤트 위임은 이벤트 버블링이 발생하는 경우에서만 가능하다.
 
💡
이벤트 위임의 장단점 이벤트 위임의 장점
  1. 유지 보수가 쉽다.
  1. 코드가 간결해진다.
이벤트 위임의 단점
  1. 이벤트 버블링이 발생하는 경우에만 사용 가능하다.
  1. 상위 요소에 이벤트 핸들러를 등록하기 때문에 이벤트 동작을 파악하는데 가독성이 떨어질 수 있다.
 

References


  1. https://dom.spec.whatwg.org/#dom-event-target
  1. https://developer.mozilla.org/ko/docs/Web/API/EventTarget
  1. https://developer.mozilla.org/ko/docs/Learn/JavaScript/Building_blocks/Events#see_also
  1. https://www.quirksmode.org/js/events_access.html
  1. https://developer.mozilla.org/ko/docs/Web/API/Event
  1. https://ko.wikipedia.org/wiki/덕_타이핑
  1. https://developer.mozilla.org/ko/docs/Web/API/Event/eventPhase
  1. https://developer.mozilla.org/ko/docs/Learn/JavaScript/Building_blocks/Events
  1. https://developer.mozilla.org/ko/docs/Web/API/Event