1. 이벤트의 개념1.1. 이벤트란?2. 이벤트 타입2.1. 이벤트의 타입의 정의2.2. 키보드 이벤트 2.2.1 키 이벤트의 발생순서 2.3. 마우스 이벤트2.3.1. Click2.3.2. dbclick 2.3.3. mousedown / mouseup2.3.4. mousemove2.3.5. mouseover/mouseout2.3.6. contextmenu2.2.7 마우스 이벤트의 발생순서2.3.8. 드래그 앤 드롭2.3.8.1. dragstart / drag2.4. 포커스 이벤트2.5. 폼 이벤트3. 이벤트 핸들러3.1. 이벤트 핸들러의 정의 3.2. 이벤트 핸들러 활용하기 3.2.1. HTML 요소의 속성으로 등록하는 방법3.2.2. DOM 요소의 프로퍼티로 등록하는 방법3.2.3. addEventListener 메서드를 이용해 등록하는 방법3.3. addEventListener3.3.1. addEventListener로 이벤트 등록하기3.3.2. removeEventListener로 이벤트 제거하기4. 이벤트 객체4.1. 이벤트 객체란?4.2. 이벤트 객체 콘솔에 출력해보기4.3. 이벤트 객체의 종류4.3.1. 공통 메서드4.3.2. 공통 프로퍼티4.4. 이벤트 객체 사용하기4.4.1. 마우스 이벤트의 프로퍼티4.4.2. 키보드 이벤트의 프로퍼티4.4.3. 마우스 이벤트 객체를 이용한 예제 실습4.4.4. 키보드 이벤트 객체를 이용한 예제 실습14.4.5. 키보드 이벤트 객체를 이용한 예제 실습25. 이벤트 전파 5.1. 이벤트 전파의 정의5.2. 이벤트 타깃5.3. 캡처링5.3.1. 이벤트 캡처링 이용하기5.4. 이벤트 버블링5.4.1. 이벤트 버블링 이용하기5.4.2. 버블링이 불가능한 이벤트 종류5.5. 이벤트 전파 막기5.5.1. e.stopPropagation()5.5.2. e.stopImmediatePropagation()5.5.3. e.preventDefault()6. 이벤트 위임6.1. 이벤트 위임6.2. 이벤트 위임의 작동 방식6.3. 이벤트 위임 사용 시 유의사항6.4. 이벤트 위임 사용 예시Reference
1. 이벤트의 개념
1.1. 이벤트란?
이벤트의 사전적 의미는 사건으로, 어떠한 사건이 발생했다는 것을 의미합니다. 웹 페이지에서 마우스 클릭을 하거나 키보드를 누르고, 화면 크기를 조절하는 사건이 발생했을 때 웹 페이지는 그에 맞는 동작을 합니다.
이벤트 발생 시 지정된 동작을 하도록 자바스크립트(JavaScript)를 통해 이벤트를 다루는 것을 이벤트 핸들링(Event Handling)이라고 하고 이벤트를 전달받아 일어나야 하는 구체적인 동작을 코드로 구현하고, 이를 등록해 실행할 수 있는데 이를 이벤트 핸들러(Event Handler)라고 합니다. 자세한 내용을 뒤에 다루도록 하고, 먼저 간단한 클릭 이벤트 예시를 살펴보도록 하겠습니다.
<!DOCTYPE html> <html> <body> <button id="myBtn">클릭하세요!</button> <script> const btn = document.querySelector('#myBtn'); btn.onclick = function(){ alert('안녕!'); } </script> </body> </html>

2. 이벤트 타입
2.1. 이벤트의 타입의 정의
브라우저에서는 수많은 이벤트를 발생시킬 수 있습니다. 우리의 일상생활 중 생기는 사건처럼 많은 이벤트가 있으며 또한 적용 방식도 다양한 종류가 있습니다.
키보드
, 마우스
, HTMLDOM
, window객체
등 처리하는 이벤트는 폭 넓게 제공되고 있으며, 각 파트마다 요소가 다양합니다..png?table=block&id=83da1fe5-fe5f-4d6c-aaf1-1f5c099aa7a6&cache=v2)
2.2. 키보드 이벤트
키보드 이벤트(Keyboard Event)는 사용자가 키보드의 키를 누르거나, 키를 땠을 때 발생하는 이벤트 입니다. 키를 누를 때
keydown
이 발생하며, 키를 놓을 때는 keyup
타입의 이벤트가 발생됩니다. 그리고 keypress
타입은 현재 권장되지 않는 타입 입니다.해당 이벤트를 순서대로 나열한 표 입니다.
이벤트 | 설명 |
keydown | 유저가 처음 키를 눌렀을 때 |
keyup | 유저가 키를 뗄 때 |
keypress | 유저가 눌렀던 키의 문자가 입력을 됐을 때 |
keydown 과 keypress 의 차이
keydown
과 keypress
는 키를 누를 때 실행이 됨을 표에서 알 수 있습니다. 하지만 이론적으로 다가가 보자면 keydown
이벤트는 키를 누르거나 해제 된 키를 나타내며 keypress
이벤트는 입력 중이었던 문자를 나타냅니다.그리고
keydown
은 모든 문자를 인식하지만, keypress
는 한글을 인식하지 않습니다.키보드 이벤트는 DOM 상에서, 브라우저 창이나 문서, 그리고 특정
element(엘리먼트)
에서 발생할 수 있습니다. 브라우저 창에서 발생되는 키보드 이벤트를 처리하려면 아래 예시와 같이 함수로 설정해주거나, callback
함수를 설정해 주어서 처리가 가능합니다.Ex 1 : window.onkeydown = function(event) { console.log(event); } Ex 2 : window.onkeydown = (event) => console.log(event);
addEventListener()
라는 함수를 이용하여 함수를 설정해서 키보드 이벤트를 실행할 수 있습니다. 단, DOM property
는 대문자, 소문자를 구분하기에 주의해야 합니다.document.querySelector('.text'). addEventListener('keydown', () => { console.log('키를 눌렀습니다.') });
우선 키보드 이벤트에 대해 예시를 통해 알아보도록 하겠습니다.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>키보드 이벤트</title> </head> <body> <input class="text" type="text"> <script> document.querySelector('.text'). addEventListener('keydown', () => { console.log('키를 눌렀습니다.') }); document.querySelector('.text'). addEventListener('keyup', () => { console.log('키를 뗐습니다.') }); document.querySelector('.text'). addEventListener('keypress', () => { console.log('눌렀던 키의 문자가 입력 되었습니다.') }); </script> </body> </html>


2.2.1 키 이벤트의 발생순서
키를 누른 순간부터
keydown
이벤트가 발생되어 , 바로 keypress
이벤트가 발생 됩니다. 그 후 누른 키를 해제하게 되면 keyup
이벤트가 발생합니다.
2.3. 마우스 이벤트
마우스 이벤트(Mouse Event)
는 마우스로 일어나는 이벤트를 뜻하며, 태블릿이나 모바일 기기에서도 적용이 됩니다. 그리고 마우스 이벤트는 하나의 동작을 했을 때 실행되었던 이벤트는 여러 개일 수도 있습니다.해당 이벤트를 순서대로 나열한 표 입니다.
이벤트 | 설명 |
click | 유저가 대상 요소 위에서 마우스 버튼을 눌렀다가 뗄 때 |
dbclick | 마우스 버튼을 두번 눌렀다가 뗄 때 |
mousedown | 마우스클릭을 누르고 있을때 |
mouseup | 눌렀던 마우스 버튼을 땔 때 |
mousemove | 마우스를 움직였을 때 |
mouseover | 요소 바깥에서 요소안으로 마우스를 움직였을 때 |
mouseout | 요소 바깥으로 마우스를 움직였을 때 |
mouseClick | 마우스 버튼을 눌렀거나 휠을 돌렸을 때 |
2.3.1. Click
클릭하면 생기는 이벤트입니다. 예제를 다뤄보도록 하겠습니다. 버튼을 클릭 할 경우 콘솔창에
클릭 하였습니다!
라는 멘트가 나오게 됩니다.<button class="button">Click me</button> <script> document.querySelector('.button').addEventListener('click', () => { console.log('클릭 하였습니다!') }); </script>


2.3.2. dbclick
더블 클릭시 발생하는 이벤트입니다. 예제를 다뤄보도록 하겠습니다. 아래 예제는
click
과 dbclick
을 다뤄 본 예제입니다. 클릭을 두 번 할 시에 클릭 하였습니다!
라는 로그가 2번 콘솔 창에 출력됨과 동시에 더블 클릭 하였습니다!
는 로그가 콘솔창에 출력됩니다.<button class="button">Click me</button> <script> document.querySelector('.button').addEventListener('click', () => { console.log('클릭 하였습니다!') }); document.querySelector('.button').addEventListener('dblclick', () => { console.log('더블 클릭 하였습니다!') }); </script>


2.3.3. mousedown / mouseup
해당 이벤트는 각각 요소를 클릭했을 때, 클릭한 다음 손가락을 떼었을 경우 발생하는 이벤트입니다. 예제를 다뤄보도록 하겠습니다.
마우스로 요소를 눌렀을 때
마우스를 누르고 있습니다.
라는 로그가 콘솔창에 출력되며, 마우스를 뗄 시에는 마우스를 뗐습니다.
라는 로그가 콘솔 창에 출력됩니다.<button class="button">Click me</button> <script> document.querySelector('.button').addEventListener('mousedown', () => { console.log('마우스를 누르고 있습니다.') }); document.querySelector('.button').addEventListener('mouseup', () => { console.log('마우스를 뗐습니다.') }); </script>


2.3.4. mousemove
해당 이벤트는 요소 안에서 마우스가 이동될 때 생기는 이벤트 입니다. 예시를 통해 다뤄보겠습니다.위 예시와 같이 마우스가 버튼 안에서 이동하게 될 시에
마우스가 요소 안에서 이동하고 있습니다.
라는 멘트를 남기게 됩니다.<button class="button">Click me</button> <script> document.querySelector('.button').addEventListener('mousemove', () => { console.log('마우스가 요소 안에서 이동하고 있습니다.') }); </script>


2.3.5. mouseover/mouseout
해당 이벤트는 마우스가 요소 밖으로 들어오거나 나가거나, 할 때 발생되는 요소 입니다. 예시를 통해 다뤄보겠습니다. 예시처럼 마우스가 요소 안쪽으로 들어오게 되면
"마우스가 요소 안으로 들어왔습니다."
라는 멘트가 뜨고 마우스가 요소를 벗어나면 "마우스가 요소 밖으로 이동했습니다."
라는 멘트가 뜨게 됩니다.<button class="button">Click me</button> <script> document.querySelector('.button').addEventListener('mouseover', () => { console.log('마우스가 요소 안으로 들어왔습니다.') }); document.querySelector('.button').addEventListener('mouseout', () => { console.log('마우스가 요소 밖으로 이동했습니다.') }); </script>


2.3.6. contextmenu
해당 이벤트는 마우스 오른쪽 클릭을 할 때 생기는 이벤트 입니다. 예시를 통해 다뤄보겠습니다.예시처럼 마우스 오른쪽 클릭을 하게 될 시에
"마우스 오른쪽 클릭을 하였습니다."
라고 멘트가 뜹니다.<button class="button">Click me</button> <script> document.querySelector('.button').addEventListener('contextmenu', () => { console.log('마우스 오른쪽 클릭을 하였습니다.') }); </script>


2.2.7 마우스 이벤트의 발생순서
마우스를 요소에 올렸을 때
mousedown
이벤트가 발생하며, 클릭을 하게되면 click
이벤트가 발생 되어 집니다. 이와 같이 요소 안을 클릭하게 된다면 mouseclick
이벤트가 동시에 발생하게 되며, 마우스를 뗐을 때 mouseup
이벤트가 발생 됩니다.해당 표는 마우스가 클릭을 하였을 때의 이벤트 발생 과정 입니다.

2.3.8. 드래그 앤 드롭
드래그(drag), 드롭(drop)은 우리가 컴퓨터를 활용하면서 이용하는 기능입니다. 아래와 같이 특정한 파일을 끌어오거나 이미지를 다운 받는 용도로도 사용합니다.

드래그 드롭은 이벤트 기반으로 작동하게 되는데, 마우스 커서로 객체를 드래그 하여 놓을 때 까지 여러 단계의 이벤트가 순차적으로 발생 하게 되어서 동작을 완료합니다. 해당 태그에 드롭 이벤트를 발생 시키려면
draggable="true"
값을 주어야 합니다. 더욱 더 자세한 내용은 뒷 부분에서 설명하도록 하겠습니다.<div draggable="true" class="drag"> <p>test</p> </div>
2.3.8.1. dragstart / drag
해당 이벤트는 객체를 드래그 하려고 시작할 때 일어나는 이벤트 이며, 드래그 중에는 “drag”라는 이벤트가 기본적으로 동작이 됩니다. 다음은 드래그 이벤트를 순서대로 나열한 표 입니다.
이벤트 | 설명 |
dragstart | 유저가 객체를 드래그 하려고 시작할 때 |
drag | 대상 객체를 드래그하면서 마우스를 움직일 때 발생 |
dragenter | 마우스가 대상 객체의 위로 처음 진입할 때 발생함 |
dragover | 드래그 하면서 마우스가 대상 객체의 영역 위에 자리 잡고 있을 때 발생함 |
drop | 드래그가 끝나서 드래그하던 객체를 놓는 장소에 위치한 객체에서 발생함. 리스너는 드래그된 데이터를 가져와서 드롭 위치에 놓는 역할을 함 |
dragleave | 드래그가 끝나서 마우스가 대상 객체의 위에서 벗어날 때 발생함 |
dragend | 대상 객체를 드래그하다가 마우스 버튼을 놓는 순간 발생함. |
예시로 알아보겠습니다. 예시와 같이 드래그가 시작될 때는
"드래그가 시작되었습니다."
라는 멘트가 뜨며, 드래그 중에 마우스를 움직이면 "드래그 하였습니다"
라는 멘트가 나옵니다.<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> <style> .drag{ width: 100px; height: 100px; background-color: aqua; } </style> </head> <body> <div draggable="true" class="drag"> <p>test</p> </div> <script> document.querySelector('.drag'). addEventListener('dragstart', () => { console.log('드래그가 시작 되었습니다..') }); document.querySelector('.drag'). addEventListener('drag', () => { console.log('드래그 하였습니다.') }); </script> </body> </html>


2.4. 포커스 이벤트
포커스 이벤트는 예를 들어 포털 사이트에 접속하면 검색창 커서가 깜빡이는 것을 볼 수 있을 것입니다. 그 해당 텍스트 박스에 입력을 할 수 있거나, 로그인 시에 ID를 입력하는 박스에 커서가 자동으로 위치하게 되는 경우라고 볼 수 있습니다. 그것을 메소드를 통해 구현한 것이 포커스 이벤트입니다.
focusin
, focusout
, addEventListener()
방식으로 이벤트를 등록해 줘야만 브라우저에서 정상적으로 작동합니다. 해당 표에는 버블링이 일어나는지에 대한 여부가 나타나 있습니다. 버블링의 대한 개념은 뒤에서 다뤄보도록 하겠습니다.
이벤트 | 설명 |
focus | HTML요소에서 포커스를 받았을 때 실행함 (버블링 X) |
blur | HTML요소에서 포커스를 잃었을 때 실행함 (버블링 X) |
focusin | HTML요소에서 포커스를 받았을 때 실행함 (버블링 O) |
focusout | HTML요소에서 포커스를 잃었을 때 실행함 (버블링 O) |
해당 예제를 실행할 경우, 텍스트 칸을 클릭하면
"포커스가 시작 되었습니다."
라는 멘트를 볼 수 있을것입니다.그리고 텍스트 박스를 벗어난 영역을 클릭한 경우,
"포커스를 벗어났습니다."
라고 멘트가 뜹니다.<input type="text" id="focus"> <script> document.querySelector('#focus'). addEventListener('focus', (e) => { console.log('포커스가 시작 되었습니다.') }); document.querySelector('#focus'). addEventListener('blur', (e) => { console.log('포커스를 벗어났습니다.') }); </script>


2.5. 폼 이벤트
폼 이벤트는 주로 사용자의 입력값이 유효한지에 대해서 많이 사용되는 이벤트입니다. 폼은 보통 HTML의 요소에서 정보를 제출하기 위해 문서 구획을 나타내는데,
input
과textarea select
와 option
의 요소에서 사용하게 됩니다. 참고로 포커스 이벤트에서 focus
와 blur
를 다뤘었습니다. 똑같은 내용이므로 참고를 해주세요.이벤트 | 설명 |
focus | 폼 요소에 포커스가 놓였을 때 이벤트가 발생 합니다. |
blur | 폼 요소에 포커스가 벗어났을 때 이벤트가 발생 합니다. |
change | 목록이나 체크 상태 등 , 변경 되었을 때 이벤트가 발생합니다. |
reset | 폼이 다시 시작되었을 때 이벤트가 발생 합니다. |
submit | submit 버튼을 눌렀을 때 이벤트가 발생합니다. |
예시를 통해
change
이벤트를 살펴 보겠습니다. 예시처럼 change 이벤트는 상태가 변경되었을 때 이벤트가 발생하므로, 텍스트 박스 안에 있는 글자가 수정되면 실행이 됩니다.<input type="text" id="id"> <input type="text" id="pw"> <script> document.querySelector('#id'). addEventListener('change', (e) => { console.log('정보가 변경 되었습니다.') }); document.querySelector('#pw'). addEventListener('change', (e) => { console.log('정보가 변경 되었습니다.') }); </script>


3. 이벤트 핸들러
3.1. 이벤트 핸들러의 정의
이벤트가 발생했을 때 이를 처리하기 위해 실행되는 함수를 “이벤트 핸들러(Event Handler)”라고 부릅니다. 만약, 사용자가 특정한 버튼을 눌렀을 때 경고창이 뜬다고 가정해 봅시다. 이는 버튼을 누르는 이벤트가 발생했을 때 경고창이 뜰 수 있도록 이벤트 핸들러로 처리해준 것입니다. 이처럼, 이벤트가 일어난 대상에 이를 어떻게 처리해야 하는지에 대한 이벤트 핸들러를 등록해주어야 합니다. 이벤트 핸들러는 다양한 방법으로 등록할 수 있습니다.

3.2. 이벤트 핸들러 활용하기
다음 목차에서는 alert창으로 결과를 출력할 수 있도록 이벤트 핸들러를 등록하는 세 가지 방법을 코드 예시를 통해 확인해 보겠습니다. 모든 방법에서 출력 결과는 같습니다.

3.2.1. HTML 요소의 속성으로 등록하는 방법
첫 번째 방법은 HTML 요소의 속성에 이벤트 핸들러를 등록하는 방법입니다. HTML 요소가 가진 다양한 속성 중에 “onkeydown”, “onkeyup”, “onclick” 과 같이 “on + 이벤트명”으로 된 이름을 가지고 있는 속성이 있다면 이는 해당 이벤트를 다루는 이벤트 핸들러 속성임을 의미합니다.
다음 예시는 Click here! 라는 버튼을 클릭하게 된다면 ‘Smile!’ 라는 경고창이 뜨게 하는 코드입니다. 이 예시에서는 이벤트 핸들러를 HTML요소의 속성으로 등록해 주었습니다. 즉 button 태그에 이벤트 핸들러를 등록한 것입니다.
<button onclick="button_click();">Click here!</button> <script> function button_click(){ alert('Smile!'); } </script>

예시에서 이벤트 핸들러는
onclick="button_click();"
부분입니다. onclick(클릭)되었을 때, 함수 button_click()
을 실행하게 됩니다.즉 예시의
button
태그는 onclick
이라는 이벤트 핸들러 속성과 함께, 괄호가 있는 button_click()
같은 함수 호출문을 태그의 속성으로 가지게 됩니다.이 방식은 대단히 오래된 방식이며, 사용이 권장되지 않습니다. 왜 그럴까요? 먼저 HTML과 JavaScript는 그 사용 목적이 각각 다르기 때문에 최근에는 이 두 언어를 분리하여 사용하는 것이 일반적입니다. HTML내에 JS코드가 혼재되어 있는 이 방식을 사용한다면 코드에 문제가 생겼거나 수정이 필요할 경우 HTML과 JavaScript를 분리해 확인하는 것을 어렵게 만듭니다. 코드가 길어져 가독성이 떨어질 수 있다는 단점 또한 존재하며 이는 유지보수를 어렵게 만드는 원인이 됩니다.
또 JavaScript는 여러 스크립트를 통합하여 하나의 JS파일만을 적용하게 됩니다. 만약 JS파일 내에 여러 스크립트가 있다면 그것들을 통합하는 과정에서 예기치 못한 오류가 발생할 가능성도 존재합니다. 이렇게 스크립트가 산발적으로 HTML 내에 존재하는 경우라면 각각의 스크립트가 잘 작동하는지 검사하기 어려워지는 만큼 오류의 가능성 또한 증가합니다.
따라서 두 언어를 분리하는 관점에서 보았을 때, HTML요소의 속성으로 이벤트 핸들러를 등록하는 방식은 일반적으로 권장되지 않습니다.
그렇다면 이 방식은 현재는 더이상 사용되지 않는 과거의 유물일 뿐일까요? 언제나 그런 것은 아닙니다. ”React”, ”Vue.js”, “Angular”같은 최근의 프레임워크와 라이브러리는 이벤트 핸들러를 인라인 방식으로 등록합니다. 따라서 개발의 목적과 환경에 따라 사용될 수도 있다는 점을 유의하면 좋겠습니다.
3.2.2. DOM 요소의 프로퍼티로 등록하는 방법
두 번째 방법은 DOM 요소의 이벤트 “프로퍼티” 를 통해 등록하는 방법입니다. HTML 태그가 다양한 속성을 가지듯이, 프로퍼티는 DOM 요소의 속성을 의미합니다. HTML 요소와 마찬가지로 ‘on+이벤트 명’으로 이루어진 DOM 요소의 프로퍼티를 통해 이벤트 핸들러를 등록할 수 있습니다.
아래 예시를 봅시다. “Click here”라는 텍스트를 가지고 있는 button 태그는 “btn”라는 class를 가지고 있습니다. button 태그에 이벤트 핸들러를 연결하기 위해 먼저, doucment의 메서드인 querySelector를 통해 “.btn”라는 class를 가진 DOM 요소를 찾습니다. 그 다음, 찾아온 DOM 요소의 “onclick”이라는 프로퍼티를 사용하여 해당 요소에 마우스를 클릭하였을 때 ‘smile!’이라는 경고창이 뜨게됩니다.
<button type="button" class="btn">Click here</button> <script> const button_click = document.querySelector('.btn'); button_click.onclick = function() { alert('smile!'); }; </script>
위에서 얘기한 HTML 요소 속성 등록 방식과 달리 프로퍼티 등록 방식은 HTML 과 JavaScript 를 분리하여 가독성과 유지보수 둘 다 가져갈 수 있는 장점이 있습니다. 하지만 이 방식 역시 이벤트를 다루는 데에 한계가 있습니다. 프로퍼티 등록 방식은 복수의 이벤트 핸들러를 등록할 수 없습니다. 즉, 하나의 이벤트에 한 개의 이벤트 핸들러만을 등록할 수 있습니다. 복수의 이벤트를 등록하더라도 두번째로 등록된 이벤트가 첫번째 이벤트를 덮어쓰게 되어 첫번째 이벤트는 실행되지 않고 두번째 이벤트만 실행을 하게 됩니다. 복수의 이벤트를 등록했을 경우를 밑에 예시로 실행해 보겠습니다.
위의 예시에서 'Be Happy!' 라는 출력값이 다른 이벤트를 하나 더 추가하였습니다. 코드를 실행하면 처음 할당된 'smile!' 출력 경고창은 실행되지 않고 두 번째 이벤트인 ‘Be Happy!’ 출력 경고창이 실행 되는것을 확인 할 수 있습니다.
<button type="button" class="btn">Click here</button> <script> const button_click = document.querySelector('.btn'); button_click.onclick = function() { alert('smile!'); }; button_click.onclick = function() { alert('Be Happy!'); }; </script>

3.2.3. addEventListener 메서드를 이용해 등록하는 방법
이벤트 핸들러 등록하기의 마지막 방법은 DOM 요소의 addEventListener 메서드를 통해 등록하는 방법입니다. 첫 번째와 두 번째 방법은 하나의 이벤트에 오직 하나의 핸들러만 할당할 수 있다는 단점이 있습니다. 하지만 addEventListener를 사용하면 하나의 표적 요소에 여러 개의 핸들러를 할당할 수 있습니다.
또한 일부 이벤트는 addEventListener메서드로 등록해야만 작동합니다. 그러니 이벤트 등록 방식을 addEventListener로 전체 통일해 주는 것을 권장합니다.
3.3. addEventListener

addEventListener는 3개의 파라미터를 갖습니다. 첫 번째 값은 이벤트 명, 두 번째 값은 핸들러 함수, 세 번째 값은 이벤트 흐름을 지정하기 위한 값으로 [5. 이벤트 전파] 챕터에서 자세히 다룰 예정입니다. 앞선 두 이벤트 등록 방법과는 다르게 이벤트 명의 경우 앞에 on이 붙지 않는다는 특징을 가지고 있습니다. 세 번째 인자 값은 boolean 형태의 값을 가지며 옵션이기 때문에 생략되어도 무관합니다. 기본 값은 false로 버블 단계에서 이벤트가 실행되고, true는 캡처 단계에서 이벤트 핸들러가 실행됩니다. [5]
3.3.1. addEventListener로 이벤트 등록하기
아래 코드를 살펴봅시다. 마찬가지로 button 태그에 이벤트 핸들러를 등록하기 위해 “btn”이라는 class를 가지고 있는 DOM 요소를 찾습니다. 찾은 DOM 요소에 addEventListener 메서드를 사용하여 이벤트 핸들러 두 개를 등록해줍니다. 첫 번째 이벤트인 event1은 첫 번째 인자 값으로 ‘마우스를 클릭했을 때’를 뜻하는 ‘click’ 이벤트 이름이 들어가고, 두 번째 인자 값으로는 핸들러 함수를 넣어주었습니다. 두 번째 이벤트는 익명 함수로 이벤트를 바로 할당해 주었습니다. 따라서, ’Click here’ 버튼을 클릭하면 ‘smile!1’ 이라는 텍스트가 경고창에 출력되고 확인 버튼을 누르면 ‘smile!2’ 텍스트가 담긴 경고창이 한번 더 나오게 됩니다.
<!DOCTYPE html> <html> <body> <button type="button" class="btn">Click here!</button> <script> function event1() { alert('smile!1'); } const button_click = document.querySelector('.btn'); button_click.addEventListener('click', event1); button_click.addEventListener('click', function() { alert('smile!2'); }); </script> </body> </html>


3.3.2. removeEventListener로 이벤트 제거하기

addEventListener메서드로 등록된 이벤트는 removeEventListener를 통해 제거가 가능합니다. removeEventListener 역시 3개의 파라미터를 갖습니다. 첫 번째 값은 이벤트 명, 두 번째 값은 핸들러 함수, 세 번째 값은 옵션으로 생략되어도 무방하며 boolean 값을 가집니다. 기본 값은 false로 제거할 이벤트가 자손에게 전파되는지에 대한 여부를 나타냅니다.
button_click.removeEventListener('click', event1); button_click.removeEventListener('click', function() { alert('smile!2'); });
이벤트를 제거할 때는 등록했던 핸들러를 인자로 똑같이 전달해주어야만 정상적으로 제거됩니다. 위 코드를 추가하여 Click here! 버튼을 다시 클릭해 보면 ‘smile!2’ 경고창만 나오는 모습을 볼 수 있습니다. 두 번째 이벤트가 제거되지 않은 이유는 등록했을 때의 함수와 제거할 함수가 각기 다른 주솟값을 가리키고 있는 다른 함수이기 때문입니다. 즉 removeEventListener가 addEventListener로 등록한 이벤트 핸들러를 찾지 못한 것입니다. 그러므로 removeEventListener메소드를 사용하려면 이벤트 핸들러를 변수나 상수에 저장해야 한다는 것을 주의해야합니다.
첫 번째와 두 번째 방법처럼 ‘on + 이벤트명’으로 등록한 핸들러를 제거할 땐 null을 할당해 주면 됩니다. 아래 코드를 참고해 주세요.
<button onclick="button_click(); = null">Click here!</button> <script> function button_click(){ alert('Smile!'); } </script>
<button type="button" class="btn">Click here</button> <script> const button_click = document.querySelector('.btn'); button_click.onclick = function() { alert('smile!'); }; button_click.onclick = null; </script>
4. 이벤트 객체
4.1. 이벤트 객체란?
이벤트 객체란 발생한 이벤트에 대한 자세한 정보를 담아 제공하는 객체입니다. 이벤트의 타입에 따라 이벤트 객체에 담기는 정보가 달라집니다. 예를 들어, 마우스 이벤트의 경우 마우스 클릭 이벤트가 발생하면 마우스가 클릭된 지점의 x좌표, y좌표에 대한 정보가 객체에 저장되고, 키보드 이벤트의 경우 키보드를 누르는 이벤트가 발생하면 눌린 키의 키코드 정보가 객체에 저장됩니다. 이벤트 객체는 이벤트 타입에 따라 가지고 있는 프로퍼티들이 조금씩 다른데 공통으로 가지고 있는 메서드와 프로퍼티도 존재합니다.
4.2. 이벤트 객체 콘솔에 출력해보기
이벤트 객체가 어떤 정보를 담고 있는지 콘솔에 직접 출력해봅시다. 이번 예제에서는 마우스 이벤트 객체가 담고 있는 정보에 대해 출력해 볼 예정입니다. 아래 코드를 보면 이벤트 핸들러의 파라미터로
event
가 들어가 있는 것을 볼 수 있습니다. 이것이 바로 이벤트 객체를 의미합니다. 웹페이지에서 특정 이벤트가 발생하면 이벤트 객체가 자동으로 생성되어 이벤트 핸들러의 첫 번째 파라미터에 전달됩니다. event
라는 파라미터 명 대신에 다른 이름으로 작성해도 되지만 보통 e
나 event
로 사용하는 것이 일반적이며, 생략할 수도 있습니다. 단, 인라인 방식으로 등록한 이벤트 핸들러는 event
라는 이름 외에 다른 이름으로 받아올 때 이벤트 객체를 전달 받지 못합니다. ‘마우스 이벤트 객체’라는 버튼을 클릭하면 콘솔창에 마우스 이벤트 객체가 출력되는 것을 확인할 수 있습니다. 이처럼, 이벤트와 관련된 다양한 정보들이 객체에 담겨있다는 사실을 알 수 있습니다.
<!DOCTYPE html> <html> <body> <button type="button" class="btn">마우스 이벤트 객체</button> <script> const buttonClick = document.querySelector('.btn'); buttonClick.addEventListener('click',function (event) { console.log(event); }); </script> </body> </html>
.png?table=block&id=f23157ad-7c7e-4081-96fb-ad61fa885ccf&cache=v2)
4.3. 이벤트 객체의 종류
Javascript의 객체는 모두 고유의 메서드와 프로퍼티를 가집니다. 따라서 이벤트 객체 또한 고유의 메서드와 프로퍼티를 가지고 있습니다. 모든 이벤트 객체를 다루기에는 그 양이 너무 방대해서 이벤트 객체들이 공통적으로 가지고 있는 메서드와 프로퍼티에 대해 살펴보고자 합니다. 이벤트 객체들이 공통적으로 가지는 메서드와 프로퍼티를 아래 표로 정리 해보았습니다. 설명 부분의 이벤트 버블링, 전파 등 이벤트 흐름과 관련된 내용들은 이후 [5. 이벤트 전파] 챕터에서 자세하게 다룰 예정입니다.
4.3.1. 공통 메서드
메서드 | 설명 |
composedPath() | 이벤트 경로 반환 |
preventDefault() | 이벤트 디폴트 행동을 취소할 수 있는 경우 취소 |
stopImmediatePropagation() | 이벤트 캡처링, 버블링 모두 취소하고 다른 이벤트 핸들러 호출을 막음 |
stopPropagation() | 이벤트 캡처링, 버블링 모두 취소 (bubbles : true일 때 동작함) |
4.3.2. 공통 프로퍼티
프로퍼티 | 설명 |
bubbles | 이벤트 버블링 여부 판단 (true/false 반환) |
cancelable | 이벤트 취소 여부 판단 (true/false 반환) |
composed | 이벤트가 섀도우 DOM과 일반 DOM의 경계를 넘어 버블링을 할 수 있는지의 여부를 판단 |
target | 이벤트가 실제로 발생되는 요소 |
currentTarget | 이벤트 핸들러가 등록된 요소 |
eventPhase | 현재 처리 중인 이벤트 흐름 단계를 나타냄 |
isTrusted | 이벤트가 브라우저에 의해 초기화 됐는지, 스크립트에 의해 초기화됐는지 나타냄 |
defaultPrevented | preventDefault() 가 호출되었는지 나타냄 |
timeStamp | 이벤트 발생 시각 (페이지 로드 이후 경과 밀리 초) |
type | 발생한 이벤트의 이름 (ex. click, mouseup, keydown…) |
섀도우 DOM이란? DOM 트리에 속한 요소에 부착할 수 있는 별도의 분리된 DOM트리입니다. 섀도우 DOM이 가진 요소와 스타일은 외부에 영향을 주지 않습니다.
4.4. 이벤트 객체 사용하기
이번 챕터에서는 이벤트 객체에 담겨있는 정보들을 어떻게 활용할 수 있는지 예제를 통해 실습 해보려고 합니다. 대표적으로 마우스 이벤트와 키보드 이벤트의 예제를 살펴볼 예정입니다. 그 전에, 마우스와 키보드 이벤트 객체에서 자주 사용되는 프로퍼티에 대한 설명을 아래 표로 정리해두었습니다.
4.4.1. 마우스 이벤트의 프로퍼티
프로퍼티 | 설명 |
button | 누른 마우스 버튼 (왼쪽 - 0 / 휠 - 1 / 오른쪽 - 2) |
clientX clientY | 커서의 브라우저 표시 영역에서의 위치 |
pageX pageY | 커서의 문서 영역에서의 위치 |
offsetX offsetY | 커서의 이벤트가 발생한 요소에서의 위치 |
screenX screenY | 커서의 모니터 화면에서의 위치 |
altkey | 이벤트 발생 시 alt 키를 눌렀는지 |
ctrlkey | 이벤트 발생 시 ctrl 키를 눌렀는지 |
shiftkey | 이벤트 발생 시 shift 키를 눌렀는지 |
metakey | 이벤트 발생 시 meta 키를 눌렀는지 (윈도우- window / mac - cmd) |
clientX
, clientY
, pageX
, pageY
, offsetX
, offsetY
, screenX
, screenY
는 모두 마우스 커서의 위치 정보를 나타내지만 기준점이 다르다는 것을 주의해야 합니다. 다음은 document
를 확대한 상태의 모니터 이미지 입니다.
clientX
clientY
프로퍼티는 브라우저 화면 표시 영역에서 마우스 커서의 위치를 나타냅니다. 스크롤에 영향을 받지 않고 항상 화면 좌측 상단 꼭지점을 (0, 0)으로 반환합니다.
pageX
, pageY
프로퍼티는 문서 전체를 기준으로 합니다. 위 이미지처럼 문서를 확대해서 보고 있다고 해도 뷰포트 밖으로 나가 보이지 않는 영역까지 계산해서 반환합니다.
offsetX
, offsetY
프로퍼티는 이벤트 발생 요소를 기준으로 합니다. 위 이미지에서 ‘잡았다 요돔!’ 책 표지에 이벤트가 발생했다면 표지 좌측 상단 꼭지점을 (0, 0) 으로 반환합니다. offset
프로퍼티도 스크롤의 영향을 받지 않습니다.
screenX
, screenY
프로퍼티는 사용자 모니터 스크린 전체를 기준으로 합니다. 브라우저 창을 이동해도 같은 값을 반환하므로 client
와의 구분을 주의해야 합니다. 4.4.2. 키보드 이벤트의 프로퍼티
프로퍼티 | 설명 |
key | 이벤트가 일어난 키가 가지고 있는 값 |
code | 이벤트가 일어난 키의 물리적인 코드 값 |
ctrlKey | 이벤트가 발생 시 ctrl 키가 눌렸는지에 대한 여부 |
shiftKey | 이벤트가 발생 시 shift 키가 눌렸는지에 대한 여부 |
altKey | 이벤트가 발생 시 alt 키가 눌렸는지에 대한 여부 |
metaKey | 이벤트가 발생 시 meta 키가 눌렸는지에 대한 여부 (window - window키, mac - cmd키를 의미함.) |
키보드 이벤트 객체의 프로퍼티 중에 keyCode는 더 이상 사용되지 않습니다. 참고로, keyCode는 키보드의 각 키에 숫자를 부여한 값입니다. 따라서, 가능하다면 keyCode 대신 key를 사용하는 것이 바람직합니다.[6]
4.4.3. 마우스 이벤트 객체를 이용한 예제 실습
마우스의 좌표 프로퍼티의 자세한 의미를 위에서 알아보았습니다. 좌표 이벤트와 더불어 자주 사용하는
button
프로퍼티를 예제를 통해서 알아보고자 합니다. button
프로퍼티를 통해 좌우로 움직이는 흔들바위를 만들어 봅시다. 전체 소스 코드와 이미지를 첨부했으니 실행 결과를 확인해보시길 바랍니다.[7]<!DOCTYPE html> <html lang="ko"> <head> <link rel="stylesheet" href="style.css"> <title>흔들바위 만들기</title> </head> <body> <img src="images/rock.png" alt="rock" class="rock"> <script> const rock = document.querySelector('.rock'); function reset() { document.querySelector('.rock').classList.remove('left'); document.querySelector('.rock').classList.remove('right'); } function rocking(event) { if (event.button === 0) { rock.classList.add('left'); console.log('왼쪽으로!'); } else if (event.button === 2) { rock.classList.add('right'); console.log('오른쪽으로!'); } setTimeout(reset, 800); } document.addEventListener('mousedown', rocking); </script> </body> </html>
* { box-sizing: border-box; } body { height: 100vh; margin: 0; background-image: url('images/BG.png'); background-repeat: no-repeat; background-size: cover; background-position: center; } .rock { position: absolute; top: 170px; left: calc(50% - 65px); width: 130px; transform-origin: bottom; transition-duration: 0.3s; } .rock.left { transform: rotate(-30deg); } .rock.right { transform: rotate(30deg); }




button
왼쪽 클릭의 프로퍼티 값은 0 이고 오른쪽 클릭 값은 2 임을 앞에서 알아보았습니다. 위 코드를 실행해 보시면 왼쪽 마우스를 클릭 시 왼쪽으로 30도 기울면서 콘솔창에 ‘왼쪽으로!’ 라는 문구가 나오고 오른쪽 마우스 클릭 이벤트도 마찬가지 입니다. 하지만 이때, 오른쪽 마우스를 클릭하게 되면 바위가 오른쪽으로 기울어짐과 동시에 메뉴창이 나타나는 것을 확인할 수 있는데 이는 브라우저의 기본 동작 때문입니다. 
브라우저 기본 동작은
preventDefault()
메소드로 막을 수 있으며 아래 코드를 추가하면 기본 동작이 막아져 더 이상 메뉴창이 나타나지 않고 정상 작 동하는 모습을 확인할 수 있습니다. 더 자세한 내용은 [5. 이벤트 전파] 챕터에서 다루도록 하겠습니다.document.addEventListener('contextmenu', function (event) { event.preventDefault(); });
4.4.4. 키보드 이벤트 객체를 이용한 예제 실습1
사용자가 누른
key
가 Enter
인지 판단하여 사용자가 input
태그에 입력한 값을 HTML 문서의p
태그에 붙여 넣는 예제를 만들어 봅시다.먼저
input
태그를 작성해줍니다. querySelector
를 통해 ‘inputText’ 와 ‘textvalue’라는 아이디를 가진 요소를 찾습니다. ‘document’에 keydown
이벤트가 발생할 경우, 조건문을 이용하여 키보드 이벤트 객체의 프로퍼티인 key
값이 Enter
와 같은지 비교합니다. 만약 key
값이 Enter
라면 사용자가 입력한 값이 ‘textvalue’라는 아이디를 가진 p
태그에 붙여집니다. 따라서, 사용자가 input
태그에 12345를 입력했다면 아래와 같이 ‘입력한 텍스트 : 12345’ 가 나타나는 모습을 볼 수 있습니다.<!DOCTYPE html> <html lang="ko"> <head> <title>Enter로 form에 입력한 내용 붙이기</title> </head> <body> <h2>Enter를 누르면 입력한 텍스트가 나옵니다.</h2> <input type="text" id="inputText" placeholder="입력해주세요"> <p id="textvalue"></p> <script> const textBox = document.querySelector('#inputText'); const textValue = document.querySelector('#textvalue'); function showText(event){ if(event.key == 'Enter') { const inputTxt = document.querySelector('#inputText').value; textValue.innerHTML = '입력한 텍스트 : ' + inputTxt; } } textBox.addEventListener('keydown', showText); </script> </body> </html>

4.4.5. 키보드 이벤트 객체를 이용한 예제 실습2
이번에는 사용자가 누른 키보드의 방향키에 따라 상자를 움직여보는 예제를 만들어봅시다.
먼저, div 태그를 통해 상자 하나를 만들어 줍니다. 그리고,
querySelector
를 통해 ‘box’라는 클래스를 가진 요소를 찾습니다. ‘document’에 keydown
이라는 키를 누르는 키보드 이벤트가 발생할 경우, 조건문을 이용하여 키보드 객체에 저장된 key
값이 ArrowDown
, ArrowLeft
, ArrowRight
, ArrowUp
중에 어떤 값과 동일한지 비교합니다. 그리고, 각각의 방향에 맞게 박스의 위치 값을 조정해줍니다. 따라서, 사용자가 누른 키보드의 방향키에 따라 상자가 위, 아래, 양 옆으로 움직이는 것을 확인할 수 있습니다. <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> <style> .box { width: 100px; height: 100px; background-color: teal; } </style> </head> <body> <h1>키보드 방향키로 박스 움직이기</h1> <div class="box"></div> <script> const box = document.querySelector(".box"); let posX = 0; let posY = 0; document.addEventListener('keydown', function(e) { if(e.key === "ArrowLeft"){ posX -= 20; box.style.transform = `translateX(${posX}px) translateY(${posY}px)`; }else if(e.key === "ArrowRight") { posX += 20; box.style.transform = `translateX(${posX}px) translateY(${posY}px)`; }else if(e.key === "ArrowUp") { posY -= 20; box.style.transform = `translateX(${posX}px) translateY(${posY}px)`; }else if(e.key == "ArrowDown"){ posY += 20; box.style.transform = `translateX(${posX}px) translateY(${posY}px)`; } }) </script> </body> </html>

5. 이벤트 전파
5.1. 이벤트 전파의 정의
이벤트 전파란 이벤트가 전달되는 단계 또는 순서입니다. 특정 요소에 대한 이벤트가 발생하면 해당 요소에 할당된 이벤트 핸들러가 동작하게 되는데 이 때 핸들러가 동작하면서 버블링(bubbling)과 캡처링(capturing)이 발생하게 됩니다. 이벤트 전파는 크게 세 가지로 나눌 수 있는데 하위 요소로 전파되는 캡처(capture)단계, 이벤트가 실제 타깃 요소에 전달되는 타깃(target)단계, 그리고 상위 요소로 전파되는 버블(bubble)단계가 있습니다.
이벤트 전파가 시작되면 제일 상단의 요소부터 시작하여 하위 요소 방향으로 전달되고 이벤트가 실제로 실행된 타깃까지 전달되는 캡처링이 일어나게 됩니다. 이후 타깃에서 다시 상단의 요소를 거쳐 최상단의 요소까지 전달되는 버블링으로 끝나게 됩니다. 즉, 이벤트 전파는 캡처링으로 시작하여 타깃을 거쳐 버블링으로 끝나게 됩니다.

이벤트 전파 시 각 요소에 할당된 이벤트 핸들러가 무조건 실행되는 것은 아닙니다.
전파 받는 요소의 핸들러가 실행 되는 조건은 처음 발생된 이벤트의 타입과 같아야 합니다.
5.2. 이벤트 타깃
브라우저에서 이벤트가 발생했을 때 실제로 이벤트가 발생한 요소를 타깃 요소라고 합니다. 타깃에는 이벤트 타깃(
event target
)과 커런트 타깃(current target
)이 있습니다. 이벤트 타깃은 이벤트가 실제로 발생한 요소를 가리키고 커런트 타깃은 현재 실행 중인 이벤트 핸들러를 가진 요소를 가리킵니다.아래 예시를 살펴보면 이벤트 버블링으로 인해 어느 요소를 클릭하든
form
요소까지 이벤트가 핸들링됩니다. 이벤트 핸들러가 form
요소에 할당되어 있기 때문에 어느 요소를 클릭해도 커런트 타깃이 form
을 가리키는 것입니다.<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title></title> <style> form { background-color: salmon; } div { background-color: paleturquoise; } p { background-color: palegreen; } </style> </head> <body> <form id="form">form <div>div <p>p</p> </div> </form> <script> form.onclick = function (event) { console.log("target = " + event.target.tagName + ", currentTarget= " + event.currentTarget.tagName); }; </script> </body> </html>
Form, DIV, P 텍스트를 차례대로 클릭하면 콘솔창에 아래와 같은 결과값이 출력됩니다.


5.3. 캡처링
캡처링은 상위 요소에서 하위 요소로 진행되는 이벤트 전파 방식입니다. 최상단 요소에서 하위 요소로 전달되며 이벤트가 실제로 발생하는 요소인 이벤트 타깃까지 전파됩니다. 실제로 캡처링 단계를 사용해야 하는 경우는 많지 않습니다.

이벤트 전파는 캡처링으로 시작하여 버블링으로 종료되는 단계를 거치는데 불필요한 과정을 줄이기 위해 사용 여부를 정해주게 됩니다.
앞서 핸들러 챕터에서 배웠듯이
addEventListener
세 번째 인자인 옵션의 값으로 capture를 사용하는데, 이는 캡처링의 사용 여부를 정해 주는 것으로 false면 버블링, true면 캡처링을 사용하게 됩니다. {capture : false / true} 로 사용되며 capture를 생략하여 false / true 불리언 값만 입력할 수도 있습니다.Capture 옵션의 기본값이 버블링인 이유
쉽게 설명하자면 1학년 1반 내부에서 문제가 발생하면 1반 담임 선생님이 가장 먼저 상황을 파악합니다. 1학년 1반에 대해 가장 잘 아는 사람은 담임 선생님이기 때문입니다. 만약 추가적인 상황 파악이 필요하다면 그 이후 교감선생님이나 교장선생님이 그 문제를 넘겨받게 됩니다.
이벤트 핸들러도 이와 같습니다. 특정 요소에 대한 자세한 사항은 그 요소에 할당된 핸들러가 가장 잘 알고 있습니다. 예를 들어
button
요소에 대한 모든 것을 알고있는 핸들러가 button
을 다루는 것이 가장 적합하기 때문에 button
을 다룰 기회를 할당된 핸들러에게 제일 먼저 부여하는 것 입니다.5.3.1. 이벤트 캡처링 이용하기
가장 안쪽의 p 텍스트를 클릭하면 순서대로 다음과 같은 일이 벌어집니다.
form
에 할당된 click 핸들러가 동작
- 내부의
div
에 할당된 click 핸들러가 동작
- 그 내부의
p
에 할당된 click 핸들러가 동작
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title></title> <style> form { background-color: salmon; } div { background-color: paleturquoise; } p { background-color: palegreen; } </style> </head> <body> <form class="form">form <div class="div">div <p class="p">p </p> </div> </form> <script> const form = document.querySelector('.form') const div = document.querySelector('.div') const p = document.querySelector('.p') form.addEventListener('click', function () { console.log('form'); }, true); div.addEventListener('click', function () { console.log('div'); }, true); p.addEventListener('click', function () { console.log('p'); }, true); </script> </body> </html>
출력 결과


5.4. 이벤트 버블링
버블링은 이벤트가 하위 요소에서 상위 요소로 거슬러 올라가며 발생하는 모양이 마치 거품(bubble)과 닮았다고 해서 붙여진 명칭입니다. 한 요소에 이벤트가 발생하면 핸들러가 작동하게 되고, 부모 요소의 핸들러도 작동합니다. 최상단 요소에 닿을 때까지 이 과정이 반복되어 각 요소에 할당된 핸들러들이 작동하게 됩니다. [8]

5.4.1. 이벤트 버블링 이용하기
가장 안쪽의 p 텍스트를 클릭하면 순서대로 다음과 같은 일이 벌어집니다.
p
에 할당된 click 핸들러가 동작
- 외부의
div
에 할당된 click 핸들러가 동작
- 그 외부의
form
에 할당된 click 핸들러가 동작
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title></title> <style> form { background-color: salmon; } div { background-color: paleturquoise; } p { background-color: palegreen; } </style> </head> <body> <form class="form">form <div class="div">div <p class="p">p</p> </div> </form> <script> const form = document.querySelector('.form') const div = document.querySelector('.div') const p = document.querySelector('.p') form.addEventListener('click', function () { console.log('form'); }); div.addEventListener('click', function () { console.log('div'); }); p.addEventListener('click', function () { console.log('p'); }); </script> </body> </html>
출력 결과


5.4.2. 버블링이 불가능한 이벤트 종류
마우스 이벤트
이벤트 | 설명 |
mouseente | Mouse가 HTML 요소 안쪽으로 이동했을 때 (버블링 X) |
mouseleavem | Mouse가 HTML 요소 바깥쪽으로 이동했을 때 (버블링 X) |
포커스 이벤트
이벤트 | 설명 |
focus | HTML요소에서 포커스를 받았을 때 실행함 (버블링 X) |
blur | HTML요소에서 포커스를 잃었을 때 실행함 (버블링 X) |
리소스 이벤트
이벤트 | 설명 |
load | 이미지, CSS, JS 파일 로드 되었을 때 실행함 |
unload | 브라우저 창이 닫기거나 페이지 언로드 일 때 실행함 |
error | 외부 파일 로드 오류 났을 경우 실행함 |
abort | 비디오, 오디오 등 미디어 파일 중단 됬을 때 실행함 |
버블링이 불가능한 이벤트
버블링 되지 않는 이벤트들은 버블링을 사용 할 수 없기에 캡처링을 사용하게 됩니다. 하지만 버블링이 되는 이벤트들로 대체가 가능하기 때문에 캡처링을 사용하는 경우는 많이 없습니다.
5.5. 이벤트 전파 막기
이벤트 전파 방지는 버블링, 캡처링으로 인해 연쇄적으로 일어나는 이벤트 핸들링을 막아줄 때 사용됩니다. 많은 요소에 핸들러를 등록 했을 때 이벤트 전파로 인해 의도치 않은 이벤트가 발생하게 될 수 있습니다. 이럴 때 이벤트 전파 방지를 사용하게 됩니다.
5.5.1. e.stopPropagation()
stopPropagation()
는 버블링 단계에서 상위 요소로 이벤트가 전달되지 못하게 막아줍니다. 단, 해당 요소에 할당된 이벤트 핸들러들은 작동됩니다.아래 예시의
p
요소에는 2개의 이벤트 핸들러가 할당되어 있습니다. p
요소에 event.stopPropagation
를 적용할 경우, p
요소에 할당된 이벤트 핸들러만 작동하고 상위 요소인 div
, form
으로의 전파는 방지되는 것을 볼 수 있습니다. [9]<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title></title> <style> form { background-color: salmon; } div { background-color: paleturquoise; } p { background-color: palegreen; } </style> </head> <body> <form class="form">form <div class="div">div <p class="p">P</p> </div> </form> <script> const form = document.querySelector('.form'); const div = document.querySelector('.div'); const p = document.querySelector('.p'); //form 영역에 클릭 이벤트 설정 form.addEventListener('click', function () { console.log("form 클릭"); }); //div 영역에 클릭 이벤트 설정 div.addEventListener('click', function () { console.log("div 클릭"); }); //p 영역에 클릭 이벤트 설정 및 이벤트 전파 방지 설정 p.addEventListener('click', function () { console.log("stopPropagation p 클릭"); event.stopPropagation(); }); //p 영역에 클릭 이벤트 설정 p.addEventListener('click', function () { console.log("p 클릭"); }); </script> </body> </html>
출력 결과


5.5.2. e.stopImmediatePropagation()
stopPropagation()
과 같이 상위 요소로 전파를 하지 못하게 방지해 주며 형제 요소의 이벤트 전파도 방지해 줍니다. 자식 요소에 2개의 이벤트가 걸려 있을 때 부모 요소의 전파를 막아주고 자식 요소의 첫 번째 이벤트만 작동을 합니다. [10]주석을 풀기 전
p
요소를 클릭하면 4개의 이벤트가 실행되는 것을 확인할 수 있습니다. 주석을 풀고 다시 p
요소를 클릭해 보면 stopImmediatePropagation
이 호출되고 p
요소의 첫 번째 이벤트만 실행됩니다.<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title></title> <style> form { background-color: salmon; } div { background-color: paleturquoise; } p { background-color: palegreen; } </style> </head> <body> <form class="form">form <div class="div">div <p class="p">P</p> </div> </form> <script> const form = document.querySelector('.form'); const div = document.querySelector('.div'); const p = document.querySelector('.p'); //form 영역에 클릭 이벤트 설정 form.addEventListener('click', function () { console.log("form 클릭"); }); //div 영역에 클릭 이벤트 설정 div.addEventListener('click', function () { console.log("div 클릭"); }); //p 영역에 클릭 이벤트 설정 및 이벤트 전파 방지 설정 p.addEventListener('click', function () { console.log("stopImmediatePropagation p 클릭"); // event.stopImmediatePropagation(); }); //p 영역에 클릭 이벤트 설정 p.addEventListener('click', function () { console.log("p 클릭"); }); </script> </body> </html>
출력 결과


5.5.3. e.preventDefault()
e.preventDefault()
는 이벤트 전파를 방지하는 것이 아니라, 태그의 고유 동작을 취소시킵니다.button
태그의 타입 중 하나인 reset의 고유 동작은 form
에 입력된 데이터를 초기화하는 것입니다. 여기에 preventDefault
를 사용하면 고유 동작을 취소하여 form
에 입력된 데이터가 초기화되지 않습니다.<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title></title> </head> <body> <form> <input type="text" /> <button type="reset" id="btn-reset">초기화</button> </form> <script> const btnReset = document.getElementById("btn-reset"); btnReset.addEventListener("click", function () { event.preventDefault(); }); </script> </body> </html>

preventDefault 사용 x (초기화 o)

preventDefault 사용 o (초기화 x)
브라우저의 기본 동작
브라우저에는 이벤트가 발생 했을 때 자동 수행되도록 지정된 동작들이 있습니다. 예를 들어 a 태그를 클릭했을 때 href에 작성된 링크로 이동하거나 마우스 우 클릭을 하면 메뉴창이 나타납니다. 이를 막고 싶다면
preventDefault()
메소드를 사용하면 됩니다. 하지만 요소들이 가지고 있는 고유의 의미를 훼손하게 될 수 있기 때문에 사용에 주의해야 합니다.preventDefault()
사용의 다양한 예시와 사용법은 공식 문서에서 확인해 볼 수 있습니다. [11]6. 이벤트 위임
6.1. 이벤트 위임
이벤트 위임이란 실제로 이벤트가 발생하는 여러 타깃 요소마다 이벤트 핸들러를 각각 등록하지 않고, 그것을 모두 포함하는 하나의 상위 DOM 요소에 이벤트 핸들러를 등록해 하위 요소를 관리하는 방법입니다.
이벤트 위임은 자바스크립트의 특정한 문법으로 존재하는 것은 아닙니다. 이벤트 리스너가 등록되어있지 않은 요소의 이벤트가 발생했을 때도 해당 요소에 접근할 수 있다는 성질을 이용한 일종의 스킬이라고 볼 수 있습니다.
우리는 앞에서 이벤트 핸들러에 대해 다루어 보았습니다. 예시를 들어 다시 한 번 확인해 보겠습니다.
<ul id="characters" class="parent-ul"> <li id="jojo" class="jojo-li" onclick="button_click()"><img class="jojo-img" src="./img/jojo.png">jojo</li> <li id="koko" class="koko-li" onclick="button_click()"><img class="koko-img" src="./img/koko.png">koko</li> <li id="lolo" class="lolo-li" onclick="button_click()"><img class="lolo-img" src="./img/lolo.png">lolo</li> <li id="wowo" class="wowo-li" onclick="button_click()"><img class="wowo-img" src="./img/wowo.png">wowo</li> </ul> <script> function button_click() { alert('이벤트가 발생되었습니다!') } </script>
예시에서는
<li>
각각의 요소에 이벤트 핸들러를 모두 등록해주었습니다. 예시와 같이 요소가 많지 않은 경우에 요소마다 이벤트 핸들러를 적용하는 데에는 큰 어려움이 없어 보입니다.하지만
<li>
요소가 1억 개까지 존재하는 경우는 어떨까요? 각 <li>
요소마다 핸들러를 등록해 주어야 할 것이고, 코드의 길이는 엄청나게 증가할 것입니다. 또 각각의 요소에 할당된 이벤트 핸들러는 메모리를 일정 부분 점유합니다. 많은 핸들러가 등록되어야 하는 만큼, 메모리를 많이 점유하여 웹페이지의 성능 저하로 이어질 수도 있습니다. 이런 상황에서 코드에 문제가 생겨 수정해야 한다면 각각의 코드에 할당된 이벤트 핸들러를 모두 수정해야 할 것이고, 결과적으로 이는 유지보수를 어렵게 만드는 원인이 됩니다.
그렇다면 이 수많은 모든 이벤트에 대해 따로따로 핸들러를 할당해 주어야 할까요?

우리는 이런 문제를 줄일 수 있도록 이벤트 위임이라는 기능을 사용할 수 있습니다.
6.2. 이벤트 위임의 작동 방식
앞에서 버블링을 통해 하위 요소에서 발생한 이벤트를 상위 요소에서 감지할 수 있다는 것을 배웠습니다. 상위 요소에 이벤트 핸들러를 할당하면 하위 노드에 각각 이벤트 핸들러를 할당할 필요 없이 상위 요소의 핸들러를 이용해 이벤트를 처리할 수 있게 됩니다.

이번에는 앞의 예시에 이벤트 위임을 적용해 보겠습니다.
<li>
요소마다 핸들러를 등록하지 않고 부모 요소에만 핸들러를 등록해 주었습니다. <ul id="characters"> <li id="jojo" class="jojo-li"><img class="jojo-img" src="./img/jojo.png">jojo</li> <li id="koko" class="koko-li"><img class="koko-img" src="./img/koko.png">koko</li> <li id="lolo" class="lolo-li"><img class="lolo-img" src="./img/lolo.png">lolo</li> <li id="wowo" class="wowo-li"><img class="wowo-img" src="./img/wowo.png">wowo</li> </ul> <script> const characters = document.getElementById('characters'); function select(e) { alert("characters") } characters.addEventListener('click', select) </script>

자식 요소에는 이벤트 핸들러를 추가적으로 등록하지 않았음에도 캡처링과 버블링을 통하여 자식 요소인 li를 클릭했을 때의 이벤트까지 핸들링이 가능한 모습을 볼 수 있습니다.
이것이 이벤트 위임을 사용하는 목적입니다. 이벤트 위임을 사용할 경우 효율적이면서도 유지보수가 용이한 설계가 가능해집니다. 하위 요소에 핸들러들이 개별적으로 등록되어 있지 않기 때문에 상위 요소에 등록된 핸들러만을 관리하면 되며, 관리할 핸들러 수 자체가 적어지기 때문에 유지보수에서 큰 이점을 가집니다. 또 위임을 사용하지 않았을 때보다 코드를 간결하게 유지할 수 있습니다.

6.3. 이벤트 위임 사용 시 유의사항
하지만 이벤트 위임 사용 시에는 추가적으로 유의해야 할 사항이 있습니다.
먼저 이벤트 위임이 적용되었을 경우 스크린 리더를 통해 각 요소를 구분하기 어려워질 수 있습니다. 이것은 웹 접근성 측면에서 단점이 될 수 있으며, 접근성이 고려되어야 하는 페이지의 경우 동적으로 많은 요소가 추가되는 상황이 아니거나 성능상 이점을 크게 필요로 하지 않는다면 이벤트 위임을 사용하지 않고 개별 요소에서 이벤트가 핸들링되도록 할 수도 있을 것입니다.
또한 기본적으로 이벤트 위임은 버블링을 통해 상위 요소에서 하위 요소의 이벤트를 감지하는 것이기 때문에 버블링이 가능한 이벤트에서 이벤트 위임을 사용할 수 있습니다.
예외적으로 폼 조작에서의
blur
타입과 같은 경우 버블링이 불가능하지만 캡쳐링 단계에서 이벤트 트리거를 작동시키는 것으로 이벤트 위임과 비슷한 효과를 낼 수 있습니다. 일반적으로는 버블링이 가능한 이벤트에서 이벤트 위임을 사용할 수 있다고 알아두면 좋을 것입니다. <form id="form"> <input type="id" placeholder="id input"> <input type="password" placeholder="password"> </form> <script> var form = document.getElementById("form"); form.addEventListener("focus", function( event ) { event.target.style.background = "pink"; }, true); form.addEventListener("blur", function( event ) { event.target.style.background = ""; }, true); </script>
또 개발자가 기능을 발휘하도록 기대한 DOM 요소가 항상 현재의 DOM 요소인 것은 아닙니다. 버블링이 가능한 경우라면 부모 - 자식 - 자손 요소 간 관계에서 실제로는 한 단계 아래의 자손 요소에서 발생한 이벤트가 자식 요소에서 발생한 것처럼 핸들링될 수 있습니다. 자식 요소에서 발생한 이벤트만을 핸들링하려고 의도한 경우에 이는 문제가 될 수 있습니다.

위의 예시에서는 이벤트 위임이 적용되어 있습니다. 만약 개발자가 ul을 클릭했을 때에만 “characters” 라는 alert창을 표시하고 싶다고 의도했다고 해도 이벤트 위임이 적용된 예시에서는
ul
내부의 li요소를 클릭해도, 또 그 하위 요소인 img
를 클릭해도 ul
을 클릭했을 때와 같이 “characters” 라는 alert창이 표시되는 것을 확인할 수 있습니다.
li
와 img
를 클릭했음에도 “characters” 라는 alert창이 표시됩니다.때문에 현재의 타깃 요소가 이벤트가 실제로 발생한 목표 요소인지 검사해야 합니다. 상위 요소에 할당한 이벤트 핸들러의 인자에
event.target
속성을 주면 실제로 이벤트가 어떤 하위 요소에서 일어났는지 확인할 수 있습니다. 아래 예시에서
event.target
속성을 이용해 목표 요소를 검사해 보겠습니다. 예시와 같이
<li>
요소 안에 있는<img>
처럼 상위 요소 안에 또 다른 하위 요소가 있는 경우, 하위 요소를 클릭했을 시에도 버블링을 통해 똑같은 이벤트가 발생되기 때문에 이런 부분을 사전에 방지해 주어야 합니다. 이벤트 핸들러는 이벤트가 발생했다는 것을 감지하고 이벤트가 실행될 수 있게 합니다. 이벤트 핸들러의 인자인 event에
event.target
속성을 지정해 주면 이벤트 핸들러가 할당돼 있는 요소와 그 하위 요소를 핸들링할 때 개발자가 의도한 타깃 요소를 정확히 확인할 수 있게 됩니다. 핸들러가 등록된 요소 범위 안의 어느 부분에서 이벤트가 발생했는지 확인할 수 있게 하는 기능을 추가로 할당해 준다고 생각하면 될 것입니다.
<ul id="characters" class="parent-ul"> <li id="jojo" class="jojo-li"><img class="jojo-img" src="./img/jojo.png">jojo</li> <li id="koko" class="koko-li"><img class="koko-img" src="./img/koko.png">koko</li> <li id="lolo" class="lolo-li"><img class="lolo-img" src="./img/lolo.png">lolo</li> <li id="wowo" class="wowo-li"><img class="wowo-img" src="./img/wowo.png">wowo</li> </ul> <div class="target"> Target : <strong class="select-target"></strong></div> <script> const characters = document.querySelector("#characters"); const select = document.querySelector("strong") characters.addEventListener('click', function (event) { select.textContent = `${event.target.className}` if (event.target.tagName === "LI") { alert(`It\'s me! ${event.target.id}!`); } }) </script>
event.target
속성을 등록해 주었습니다. 이 예시에서는 클릭 이벤트가 발생합니다. 모든 경우에서 이벤트 타깃이 “Target : ”텍스트 다음에 표시되도록 했습니다.
.target
속성을 통해 원래 목표했던 요소에서 이벤트가 발생한 것을 확인할 수 있게 되며, <li>
를 클릭했을 때에만 alert창이 나타나 각 캐릭터의 id를 표시하고 이외의 경우에는 alert창이 뜨지 않도록 이벤트가 핸들링됩니다. 실행 결과는 다음과 같은 것을 확인할 수 있습니다.

클릭하는 요소에 따라 다른 결과가 나타나는 것을 확인할 수 있습니다. 따라서 이벤트 위임으로 인해 target 요소가 달라질 수 있음에 유의하며 사용할 필요가 있을 것입니다.
6.4. 이벤트 위임 사용 예시
그럼 실제로 이벤트 위임을 사용했을 때 어떻게 위와 같은 이점을 가질 수 있는지 알아보겠습니다.
아래 예시에서는 많은
<li>
요소들에 각각 인라인 이벤트 핸들러 속성을 할당해 주었습니다. <ul id="btn-all"> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="btn();" class="btn">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> <li type="button" onclick="link();" class="link">button</li> </ul> <script> function btn(){ alert('button') } function link(){ alert('link') } </script>
많은 수의
li
요소들이 존재합니다. 만약 이벤트 핸들러를 가진 요소를 추가하고 싶다면 .createElement
, .setAttribute
, .classList
등의 스크립트를 추가로 작성해 각 요소의 클래스, 타입, 이벤트 핸들러 속성 등을 추가해야 할 것이고, 매우 번거로운 작업이 될 것입니다. 코드의 양이 많을수록 수정이 제대로 이루어지기 힘듭니다.위와 같은 결과를 출력하는 코드에 이번에는 이벤트 위임을 적용하고, 추가로
addEventListener
메서드를 이용해 이벤트 핸들러를 등록해 보겠습니다.<ul class="btn"> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> </ul> <ul class="link"> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> <li type="button">button</li> </ul> <script> const btn = document.querySelector('.btn') const link = document.querySelector('.link') btn.addEventListener('click', (e) => { alert('button') }) link.addEventListener('click', (e) => { alert('link') }) </script>
같은 이벤트를 핸들링하는 자식 요소들끼리 묶어 부모 요소에만 이벤트 핸들러를 등록해 주었습니다.
후에 동적으로
<ul>
의 하위 요소 <li>
가 추가된다고 하더라도 이는 정상적으로 작동합니다. DOM내에서는 요소가 동적으로 추가, 제거되는 경우가 많습니다. 이 때 이벤트 위임을 사용하지 않는다면 하위 요소의 변동에 따라 이벤트 핸들러 또한 매번 등록하고 삭제해 주어야 합니다.이렇게 매번 등록과 삭제를 하는 것은 시스템 자원 사용에서 비효율을 발생시킵니다. 요소의 동적인 변동이 생길 때마다 이벤트 핸들러를 할당하는 과정에서 시스템의 연산자원을 소모하게 되기 때문입니다.
이벤트 위임을 사용한다면 이런 불필요한 과정들을 줄여 메모리 사용과 누수를 줄일 수 있습니다. 부모 요소에 등록된 이벤트 핸들러를 통해 이벤트 핸들링이 가능하기 때문에 일일이 이벤트 핸들러를 등록해 줄 필요가 없습니다. 이로써 시스템 자원을 더욱 효율적으로 사용할 수 있게 됩니다.
또 수정이 필요한 경우에는 부모 요소에 등록된 이벤트 핸들러와 스크립트만 수정해 주면 되기 때문에 모든 요소를 일일이 수정하지 않아도 해당 이벤트가 핸들링되는 모든 요소에서 정상적으로 작동할 수 있게 됩니다. 모든 속성을 수정해야 하는 인라인 속성에 비해 유지보수에서의 이점을 가질 수 있습니다.
다음은 이벤트 위임을 사용해 만든 간단한 투두 리스트 예제입니다.
투두 리스트의 경우에는 할 일 목록이 계속 추가되기 때문에 요소가 추가될 때마다 이벤트 핸들러를 각각 요소에 등록하지 않고 ul에만 이벤트 핸들러를 등록하는 것으로 이벤트 위임을 사용했습니다. 위 예제와 같은 방식으로 각 요소의 이벤트를 효율적으로 핸들링할 수 있습니다.
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://kit.fontawesome.com/2954c025eb.js" crossorigin="anonymous"></script> <title>TODO List</title> <style> .ir_hidden { position: absolute; clip-path: inset(50%); width: 1px; height: 1px; margin: -1px; overflow: hidden; } .container { border: 1px solid black; width: 250px; text-align: center; margin: 100px auto; border-radius: 10px; } .list { margin: 0; padding: 0; list-style: none; } .item { padding: 5px; } .done { text-decoration: line-through red; } </style> </head> <body> <div class="container"> <h1>오늘의 할 일</h1> <label for="inp_todo" class="ir_hidden"></label> <input id="inp_todo" placeholder="할 일을 입력하세요" maxlength="20"> <button type="button"><i class="fas fa-arrow-right"></i></i></button> <ul class="list"> <li class="item">아침 챙겨 먹기</li> <li class="item">강아지 산책시키기</li> <li class="item">영어 단어 외우기</li> <li class="item">commit 추가하기</li> <li class="item">명상하기</li> </ul> </div> <script> const list = document.querySelector('.list'); const btn = document.querySelector('button'); const inpValue = document.querySelector('#inp_todo') list.addEventListener('click', (event) => { const target = event.target target.classList.toggle('done') }) btn.addEventListener('click', createTodo) function createTodo() { if (inpValue.value) { const li = document.createElement('li'); li.textContent = inpValue.value; li.classList.add('item') list.appendChild(li) inpValue.value = null } else { console.log('bye') } } </script> </body> </html>

Reference
1. 이벤트
2. 이벤트 타입
3. 이벤트 핸들러
4. 이벤트 객체
5. 이벤트 전파
6. 이벤트 위임
[13]https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation