40.1 이벤트 드리븐 프로그래밍
🎯 어플리케이션이 특정 타입의 이벤트에 대해 반응하여 어떤 일을 하고 싶다면?
⇒ 해당하는 타입의 이벤트가 발생했을 때 호출될 함수를 브라우저에 알려 호출을 위임한다.
이벤트 핸들러 이벤트 핸들러 등록
이 때, 함수를 언제 호출할 지 알 수 없으므로 개발자가 명시적으로 함수를 호출하는 것이 아니라 브라우저에게 함수 호출을 위임하는 것이다.
이벤트와 그에 대응하는 함수를 통해 사용자와 애플리케이션은 상호작용을 할 수 있으며, 프로그램의 흐름을 이벤트 중심으로 제어하는 프로그래밍 기법을 ‘이벤트 드리븐 프로그래밍’ 이라고 한다.
40.2 이벤트 타입
이벤트 타입은 이벤트의 종류를 나타내는 문자열로, 약 200여 가지가 존재한다.
- 마우스 이벤트
- 키보드 이벤트
- 포커스 이벤트
- 폼 이벤트
- 값 변경 이벤트
- DOM 뮤테이션 이벤트
- 뷰 이벤트
- 리소스 이벤트
40.3 이벤트 핸들러 등록
이벤트 핸들러는 이벤트가 발생했을 때 브라우저에 호출을 위임한 함수다.
⇒ 즉, 이벤트가 발생하면 브라우저에 의해 호출될 함수!
- 이벤트 핸들러 어트리뷰트 방식
: on 접두사와 이벤트의 종류를 나타내는 이벤트 타입으로 이루어져 있다.
이벤트 핸들러 어트리뷰트 값으로 함수 호출문 등의 문을 할당하면 이벤트 핸들러가 등록된다.
<!DOCTYPE html> <html> <body> <button onclick="sayHi('lee')"> Click me! </button> <script> function sayHi(name) { console.log(`Hi! ${name}`); } </script> </body> </html>
이벤트 핸들러의 어트리뷰트 값으로 할당한 문자열은.. 사실 암묵적으로 생성될 이벤트 핸들러의 함수 몸체!
function onclick(event) { sayHi('lee'); }
💥주의할 점
- 이벤트 핸들러 프로퍼티 방식
: window 객체, Document, HTMLElement 타입의 DOM 노드 객체는 이벤트에 대응하는 이벤트 핸들러 프로퍼티를 가진다.
const $button = document.querytSelector('button'); $button.onclick = function() { console.log('button click'); }
이벤트 타깃 이벤트 타입 이벤트 핸들러
$button .onclick = function() {
console.log('button click');
}
이벤트 핸들러는 이벤트를 발생시킬 이벤트 타깃 또는 전파된 이벤트를 캐치할 DOM 노드 객체에 바인딩한다.
이벤트 핸들러 어트리뷰트 방식에서의 HTML, 자바스크립트 혼재 문제를 해결할 수 있지만, 하나의 이벤트에 하나의 이벤트 핸들러만 바인딩될 수 있다.
(여러 개가 바인딩된 경우, 재할당이 되어 마지막에 작성된 이벤트 핸들러만 작동된다.)
const $button = document.querySelector('button'); $button.onclick = function() { console.log("button clicked 1"); } $button.onclick = function() { console.log("button clicked 2"); } // button clicked 2 출력!
- addEventListener 메서드 방식
: 이벤트 핸들러 프로퍼티 방식은 이벤트 핸들러 프로퍼티에 이벤트 핸들러를 바인딩하지만, addEventListener 메서드에는 이벤트 핸들러를 인수로 전달한다.
또한, 이벤트 핸들러 프로퍼티 방식과는 다르게 하나 이상의 이벤트 핸들러를 등록할 수 있으며, 등록된 순서대로 호출된다.
const $button = document.querySelector('button'); $button.addEventListener('click', function() { console.log("button clicked 1"); }); $button.addEventListener('click', function() { console.log("button clicked 2"); }); // button clicked 1 // button clicked 2 출력!
40.4 이벤트 핸들러 제거
addEventListener 메서드로 등록한 이벤트 핸들러를 제거하려면 removeEventListener를 사용하며, addEventListener 메서드에 전달한 인수와 동일하게 작성해야 정상적으로 제거된다.
const $button = document.querySelector('button'); const handleClick = () => console.log("button clicked"); $button.addEventListener('click', handleClick); $button.removeEventListener('click', handleClick, true); // 제거 실패 $button.removeEventListener('click', handleClick); // 제거 성공
무명 함수를 등록한 경우에는 이벤트 핸들러를 참조할 수 없다.
함수 자신을 가리키는 arguments.callee를 사용할 수는 있지만, 코드 최적화를 방해하므로 strict mode에서 사용이 금지된다.
$button.addEventListener('click', () => console.log("hi")); // 제거 불가 $button.addEventListener('click', function() { console.log("hi"); $button.removeEventListener('click', argument.callee); }); // 제거 가능
가급적 제거를 위해서는 이벤트 핸들러의 참조를 자료구조에 저장하는 편이 좋다!
기명 함수를 등록한 경우에는 그 내부에서 이벤트를 제거할 수 있다.
$button.addEventListener('click', function foo() { console.log("Hi"); $button.removeEventListener('click', foo); }); // 제거 가능
이벤트 핸들러 프로퍼티 방식으로 등록한 이벤트 핸들러는 null 할당을 통해 제거할 수 있다.
const $button = document.querytSelector('button'); $button.onclick = function() { console.log('button click'); } $button.onclick = null;
40.5 이벤트 객체
이벤트가 발생하면 이벤트에 관련된 다양한 정보를 담고 있는 이벤트 객체가 동적으로 생성된다.
생성된 이벤트 객체는 이벤트 핸들러의 첫 번째 인수로 전달된다.
이 때, 이벤트 객체를 전달받기 위해서는 이벤트 핸들러를 정의할 때 이벤트 객체를 전달받을 매개변수를 명시적으로 선언해야 한다.
function showCoords(event) { console.log(`clientX; ${e.clientX}, clientY: ${e.clientY}`); } document.onclick = showCoords;
- 이벤트 객체의 상속 구조
<html> <body> <ul id="fruits"> <li id="apple">Apple</li> <li id="banana">Banana</li> <li id="orange">Orange</li> </body> </html>
캡처링 단계
: li 요소를 클릭하면 클릭 이벤트가 발생하여 클릭 이벤트 객체가 생성되고, 클릭된 li 요소가 이벤트 타깃이 된다. 생성된 클릭 이벤트 객체는 window에서 시작해서 이벤트 타깃 방향으로 전파된다.
타깃 단계
: 캡처링 단계 이후 이벤트 객체가 이벤트를 발생시킨 이벤트 타깃에 도달한다.
버블링 단계
: 타깃 단계 이후 이벤트 객체는 이벤트 타깃에서 시작해서 window 방향으로 전파된다.
이벤트 핸들러 어트리뷰트/프로퍼티 방식으로 등록한 이벤트 핸들러는 타깃 단계와 버블링 단계의 이벤트만 캐치할 수 있다.
addEventListener 방식으로 등록한 이벤트 핸들러는 타깃 단계와 버블링 단계뿐만 아니라 캡처링 단계의 이벤트도 선별적으로 캐치할 수 있다. (3번째 인수로 true 전달 필요)
40.7 이벤트 위임
여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하는 대신 하나의 DOM 요소에 이벤트 핸들러를 등록하는 방법
XXX 모든 요소에 이벤트 핸들러를 등록하면 성능 저하와 유지보수가 어렵다 XXX
<body> .. <ul id="fruits"> <li id="apple">Apple</li> <li id="banana">Banana</li> <li id="orange">Orange</li> <ul> .. </body> <script> function active({ target }) { ... } document.querySelector("#apple").onclick = active; document.querySelector("#banana").onclick = active; document.querySelector("#orange").onclick = active; </script>
OOO 이벤트 위임 OOO
상위 DOM 요소에 이벤트를 바인딩한 경우 이벤트 객체의 target 프로퍼티와 currentTarget 프로퍼티가 다른 DOM 요소를 가리킬 수 있다.
<body> .. <ul id="fruits"> <li id="apple">Apple</li> <li id="banana">Banana</li> <li id="orange">Orange</li> <ul> .. </body> <script> const $fruits = document.querySelector("#fruits"); function active({ target }) { ... if(!target.matches('#fruits > li') return; // 원하는 자식 요소가 아닐 경우 무시 } $fruits.onclick = active; </script>

currentTarget 프로퍼티: 언제나 변함없이 #fruits 을 가리킴
target 프로퍼티: #fruits에서 하위 요소 모두 클릭 이벤트가 발생할 수 있기 때문에, 값이 변경됨
40.8 DOM 요소의 기본 동작 조작
- DOM 요소의 기본 동작 중단
: 이벤트 객체의 preventDefault 메서드는 DOM 요소의 기본 동작을 중단시킨다.
<html> ... <a href="https://www.google.com">go google</a> ... </html> <script> document.querySelector("a").addEventListener('click', (e) => { e.preventDefault(); // a 태그 클릭 시, 해당 링크로 이동하는 기본 동작을 중단한다. } </script>
- 이벤트 전파 방지
: 이벤트 객체의 stopPropagation 메서드는 이벤트 전파를 중지시킨다. 하위 DOM 요소의 이벤트를 개별적으로 처리하기 위함이다.
<html> ... <ul id="fruits"> <li id="apple">Apple</li> <li id="banana">Banana</li> <li id="orange">Orange</li> <ul> ... </html> <script> document.querySelector("#fruits").onclick = ({ target }) => { if(!target.matches("#fruits" > li) return; target.style.color = "red"; } document.querySelector("orange").onclick = e => { e.stopPropagation(); // 이벤트를 전파하지 않으므로 상위 요소에서 알 수 없다. e.target.style.color = "blue"; } </script>
40.9 이벤트 핸들러 내부의 this
- 이벤트 핸들러 어트리뷰트 방식
: 일반 함수로 호출되기 때문에 함수 내부의 this는 전역 객체를 가리킨다.
<html> <body> <button onclick="handleClick()">click me</button> </body> </html> <script> function handleClick() { console.log(this); // window } </script>
<html> <body> <button onclick="handleClick(this)">click me</button> </body> </html> <script> function handleClick(button) { console.log(button); // 이벤트를 바인딩한 button 요소 console.log(this); // window } </script>
- 이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식
: 이벤트를 바인딩한 DOM 요소를 가리킨다.
<html> <body> <button id="btn">click me</button> </body> </html> <script> const $button = docunment.querySelector("#btn"); $button.onclick = function handleClick(e) { console.log(this); // $button console.log(e.currentTarget); // $button } </script>
<html> <body> <button id="btn">click me</button> </body> </html> <script> const $button = docunment.querySelector("#btn"); $button.addEventListener('click', function handleClick(e) { console.log(this); // $button console.log(e.currentTarget); // $button }); // 화살표 함수로 작성한 경우, 함수 자체의 바인딩을 갖지 않으므로 상위 스코프의 this! $button.addEventListener('click', e => { console.log(this); // window console.log(e.currentTarget); // $button }); </script>
40.10 이벤트 핸들러에 인수 전달
이벤트 핸들러 어트리뷰트 방식은 함수 호출문을 사용할 수 있기 때문에 인수를 전달할 수 있지만, 이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식은 함수 자체를 등록해야 하기 때문에 인수를 바로 전달할 수 없다.
40.11 커스텀 이벤트
- 커스텀 이벤트 생성
Event, UIEvent, MouseEvent 같은 이벤트 생성자 함수를 호출하여 명시적으로 생성한 이벤트 객체는 임의의 이벤트 타입을 지정할 수 있다.
이벤트 객체 고유의 프로퍼티 값을 지정하려면 이벤트 생성자 함수의 두 번째 인수로 프로퍼티를 전달한다.
const customEvent = new MouseEvent('click'); console.log(customEvent.type); // click console.log(customEvent.bubbles); // 기본값 false, 버블링 X console.log(customEvent.cancelable); // 기본값 false, 취소 X const mouseEvent = new MouseEvent('click', { bubbles: true, cancelable: true, clientX: 50, clientY: 100 });
- 커스텀 이벤트 디스패치
dispatchEvent 메서드로 디스패치(이벤트를 발생시키는 행위) 할 수 있다.
일반적으로 이벤트 핸들러는 비동기 처리 방식으로 동작하지만, dispatchEvent 메서드는 이벤트 핸들러를 동기 처리 방식으로 호출한다.

