브라우저는 스스로 감지해서 특정 사건이 발생하면 해당 이벤트를 발생시킨다
특정 이벤트에 대해 어떤 일을 하고 싶다면
우리는 브라우저에게 특정 이벤트가 발생 했을 때 호출될 함수를 알려주어 브라우저에게 호출을 위임한다
왜냐하면 우리는 특정 이벤트가 언제 발생할지 알 수 없으므로, 이벤트를 감지할 수 있는 브라우저에게 전적으로 위임하는 것
이벤트와 이벤트 핸들러를 통해 사용자와 애플리케이션은 상호작용한다
프로그램의 흐름을 이벤트 중심으로 제어하는 프로그램이 방식을 이벤트 드리븐 프로그래밍 이라함
이벤트의 종류를 나타내는 문자열
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| click | |
| dbclick | |
| mousedown | |
| mouseup | |
| mousemove | |
| mouseenter | 마우스 커서를 HTML 요소 안으로 이동했을 때 버블링 X |
| mouseover | 마우스 커서를 HTML 요소 안으로 이동했을 때 버블링 O |
| mouseleave | 마우스 커서를 HTML 요소 밖으로 이동했을 때 버블링 X |
| mouseout | 마우스 커서를 HTML 요소 안으로 이동했을 때 버블링 O |
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| keydown | command,option,control,shift 키는 1번만 발생
나머지 키들은 누르고 있으면 계속 발생 |
| keyup | 누른 키를 놓았을 때 1번 발생 |
| keypress | command,option,control,shift,delete,esc 키는 이벤트 발생 안함, 폐지되었으므로 사용 권장X |
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| focus | HTML 요소가 포커스 받았을 때
버블링 X |
| blur | HTML 요소가 포커스를 잃었을 때 버블링 X |
| focusin | 포커스 받았을 때 버블링 O |
| focusout | 포커스 잃었을 때 버블링 O |
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| submit | form 요소 내의 sumbit 버튼을 클릭했을 때 |
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| input | input, select, textarea 요소의 값이 입력됐을 때
입력을 하고 있을 때 발생 |
| change | input, select, textarea 요소의 값이 변경됐을 때
입력하던 HTML요소에서 포커스를 잃었을 때 발생 |
| readystatechange | HTML 문서의 파싱 상태인 document.readyState가 변경될 때
'loading', 'interactive', 'complete' |
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| DOMContentLoaded | HTML 문서의 로드와 파싱이 완료되어 DOM 생성이 되었을 때 |
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| resize | 브라우저 윈도우 크기를 리사이즈 할 때 연속적으로 발생
window 객체에서만 발생 |
| scroll | 스크롤할 때 연속적으로 발생 |
| 이벤트 타입 | 이벤트 발생 시점 |
|---|---|
| load | DOMContentLoaded 이벤트 발생 이후, 모든 리소스 로딩이 완료됐을 때
주로 winodw 객체에서 발생 |
| unload | 리소스가 언로드될 때
주로 새로운 웹페이지를 요청한 경우 |
| abort | 리소스 로딩이 중단되었을 때 |
| error | 리소스 로딩이 실패했을 때 |
이벤트 핸들러를 등록하는 방법은 총 3가지
HTML 요소의 어트리뷰트 중에는 이벤트 핸들러 어트리뷰트가 있음
이벤트 핸들러 어트리뷰트 이름 : on + 이벤트 타입 ex) onchange
이벤트 핸들러 어트리뷰트에 함수 호출문을 할당하면 이벤트 핸들러가 등록됨
<!DOCTYPE html>
<html>
<body>
<button onclick="handleClick()">버튼</button>
<script>
handleClick = () => console.log('Click');
</script>
</body>
</html>
함수 호출문을 할당하면 함수 호출문의 평가 결과가 이벤트 핸들러로 등록됨
이 때 어트리뷰트의 값인 handleClick()은 암묵적으로 생성될 이벤트 핸들러의 함수 몸체를 의미
onclick="handleClick( )" 어트리뷰트는 파싱되어 암묵적으로 함수를 생성한 뒤 , 해당 요소의 onclick 이벤트 핸들러 프로퍼티(이벤트 핸들러 어트리뷰트의 이름과 동일한 )에 암묵적으로 생성한 함수를 할당함
function onclick(){
console.log('Click');
}
// 이런 함수가 암묵적으로 생성이 되고 요소의 onclick 프로퍼티에 바인딩
이렇게 동작하는 이유는 이벤트 핸들러에 인수를 전달하기 위해서
// 인수를 전달할 수 없다
<button onclick="handleClick">버튼</button>
바닐라 자바스크립트에서는 이벤트 핸들러 어트리뷰트 방식을 사용하지 않는 것이 좋다
관심사가 다른 HTML과 자바스크립트를 혼용하기 때문에
하지만 React/Vue/Angular/Svelte/의 경우 이벤트 핸들러 어트리뷰트 방식으로 이벤트를 처리함
Component Based Development 방식에서는 HTML,CSS,자바스크립트 모두 뷰를 구성하기 위한 구성요소로 보기 때문에 관심사가 같다고 봄
{ /* React */ }
<button onClick={handleClick}>버튼</button>
window 객체, Document, HTMLElement 타입의 DOM 노드 객체에는 이벤트 핸들러 프로퍼티가 있음
이벤트 핸들러 프로퍼티 키 : on + 이벤트타입 ex) onclick
이벤트 핸들러 프로퍼티에 함수를 바인딩하면 이벤트 핸들러가 등록됨
<!DOCTYPE html>
<html>
<body>
<button>버튼</button>
<script>
const $btn = document.querySelector('button');
$btn.onclick = () => console.log('click!');
</script>
</body>
</html>
이벤트 타깃(이벤트를 발생시킬 객체)은 button 요소
이벤트 타입은 'click'
이벤트 핸들러는 대부분 이벤트를 발생시킬 이벤트 타깃에 바인딩 하지만
이벤트 위임의 경우 해당 이벤트 흐름에 있는 모든 DOM 요소에서 이벤트를 캐치할 수 있기 때문에 이벤트 타깃이 아닌 상위 DOM 요소에 이벤트 핸들러를 바인딩함
이벤트 핸들러 프로퍼티에는 단 하나의 이벤트 핸들러만 바인딩 가능
<!DOCTYPE html>
<html>
<body>
<button>버튼</button>
<script>
const $btn = document.querySelector('button');
$btn.onclick = () => console.log('click!'); // 실행 X
$btn.onclick = () => console.log('재할당 click!');
</script>
</body>
</html>
1번째 인수인 이벤트 타입은 on 없이 ex) click
3번째 인수는 이벤트를 캐치할 전파 단계를 설정
<!DOCTYPE html>
<html>
<body>
<button>버튼</button>
<script>
const $btn = document.querySelector('button');
$btn.addEventListener('click',()=>console.log('click'));
</script>
</body>
</html>
addEventListener 메서드로 등록한 이벤트 핸들러는 이벤트 핸들러 프로퍼티에 바인딩된 이벤트 핸들러와 별개다
즉 둘 다 등록 되어있다면 둘 다 실행된다
또한 중복해서 addEventListener로 여러개를 등록하면 등록된 순서대로 모두 호출됨
but 참조가 동일한 함수를 중복 등록하면 한개만 등록
<!DOCTYPE html>
<html>
<body>
<button>버튼</button>
<script>
const $btn = document.querySelector('button');
$btn.onclick = () => console.log('.onclick 프로퍼티에 등록');
$btn.addEventListener('click',()=>console.log('addEventListner로 등록'));
$btn.addEventListener('click',()=>console.log('addEventListner로 등록 2'));
$btn.addEventListener('click',()=>console.log('addEventListner로 등록 3'));
$btn.addEventListener('click',handleClick);
$btn.addEventListener('click',handleClick); // 중복 등록 X
function handleClick() {
console.log('handleClick');
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<button>버튼</button>
<script>
const $btn = document.querySelector('button');
// 딱 1번만 호출되는 이벤트 핸들러
$btn.addEventListener('click',function handleClick(){
console.log('handleClick');
$btn.removeEventListener('click',handleClick);
})
$btn.onclick = () => console.log('click'); // 등록
$btn.onclick = null; // 제거
</script>
</body>
</html>
이벤트 발생 시 해당 이벤트에 관한 정보를 담고 있는 이벤트 객체가 생성되고
이벤트 핸들러의 첫 번째 인수로 전달됨
따라서 이벤트 객체를 전달받으려면 이벤트 핸들러를 정의할 때 이벤트 객체를 받을 매개변수를 선언해야함
<!DOCTYPE html>
<html>
<body>
<p>클릭한 화면의 좌표값을 알려드림슨</p>
<p id="coordi"></p>
<script>
const $p = document.getElementById('coordi');
const printCoordi = (e) => {
$p.textContent = ` x = ${e.clientX}, y = ${e.clientY}`;
}
document.addEventListener('click',printCoordi);
</script>
</body>
</html>
이벤트 핸들러 어트리뷰트로 이벤트 핸들러를 등록했다면
어트리뷰트 값으로 할당하는 문의 매개변수 이름을 event로 해야만 이벤트 객체를 받을 수 있음
<!DOCTYPE html>
<html onclick="printCoordi(event)"> // event가 아니면 이벤트 객체 못 받음
<body>
<p>클릭한 화면의 좌표값을 알려드림슨</p>
<p id="coordi"></p>
<script>
const $p = document.getElementById('coordi');
const printCoordi = (e)=>{
$p.textContent = ` x = ${e.clientX}, y = ${e.clientY}`;
}
</script>
</body>
</html>
이벤트 타입에 따라 다양한 이벤트 객체가 생성됨

위 사진 모두 생성자 함수이므로 생성자 함수를 호출하여 이벤트 객체 생성 가능
이벤트가 발생하면 생성되는 이벤트 객체도 생성자 함수에 의해 생성됨
CustomEvent 객체는 인위적으로 생성해야 생김
| 공통 프로퍼티 | 설명 | 타입 |
|---|---|---|
| type | 이벤트 타입 | string |
| target | 이벤트를 발생시킨 DOM 요소 | DOM 요소 노드 |
| currentTarget | 이벤트 핸들러가 바인딩된 DOM 요소 | DOM 요소 노드 |
| eventPhase | 이벤트 전파 단계 0:이벤트 없음, 1: 캡처링, 2:타깃 , 3:버블링 | number |
| bubbles | 이벤트를 버블링으로 전파하는지 여부 | boolean |
| cancelable | preventDefault()로 기본 동작 취소 가능 여부 | boolean |
| defaultPrevented | preventDefault 호출 여부 | boolean |
| isTrusted | 사용자에 의해 발생한 이벤트면 true | boolean |
| timeStamp | 이벤트 발생 시각 (1970.1.1.00:00:0 부터 경과한 ms) | number |
MouseEvent 타입의 이벤트 객체가 갖는 고유의 프로퍼티는 아래와 같다
KeyboardEvent 타입의 이벤트 객체가 갖고 있는 고유의 프로퍼티는 아래와 같다
<!-- Enter키를 누르면 input에 입력된 값을 <p>안에서 보여주는 예제 -->
<!DOCTYPE html>
<html>
<body>
<input type="text">
<p class="message"></p>
<script>
const $input = document.querySelector('input[type=text]');
const $msg = document.querySelector('.message');
$input.addEventListener('keydown',e => {
if (e.key!=='Enter' || e.isComposing) return; // 한글 입력 시 이벤트 2번 발생 방지
$msg.textContent = e.target.value;
e.target.value = '';
});
</script>
</body>
</html>

따라서 해당 이벤트 패스에 있는 모든 DOM 요소는 이벤트를 캐치할 수 있다
다수의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하지 말고 그 요소들을 포함하는 상위 DOM 요소에 이벤트 핸들러를 딱 한번 등록하는 것
주의사항
내가 원하지 않았던 요소에서 이벤트가 발생할 수도 있음
ex) 여러개의 li태그에서의 click이벤트를 처리하기위해 ul태그에 이벤트를 위임 했을 때 li태그의 영역이 아닌 ul태그의 영역에서 click하게되면 원하지 않는 상황이 발생한다
따라서 이벤트 핸들러에서 이벤트가 발생한 요소를 한번 더 체크해야한다
<!DOCTYPE html>
<html>
<head>
<style>
.red{
color: red;
}
.blue{
color: blue;
}
li{
cursor: pointer;
}
</style>
</head>
<body>
<ul id="table">
<li>1번</li>
<li>2번</li>
<li>3번</li>
<li>4번</li>
<li>5번</li>
</ul>
<p></p>
<script>
const table = document.getElementById('table');
table.addEventListener('click',e=>{
if (! e.target.matches('#table > li')) return // 이벤트 타깃 검증
document.querySelector('p').textContent = `${e.target.firstChild.nodeValue} 선택`;
[...table.children].forEach(li =>{
if (li===e.target) li.className = 'red';
else li.className = 'blue';
});
});
</script>
</body>
</html>

이벤트 객체의 preventDefault 메서드는 DOM 요소의 기본 동작을 중단시킴
<!-- <a> 클릭해도 네이버 홈페이지로 이동하지 않음 -->
<!DOCTYPE html>
<html>
<body>
<a href="https://www.naver.com">Naver</a>
<script>
const atag = document.querySelector('a');
atag.addEventListener('click', e => e.preventDefault());
</script>
</body>
</html>
이벤트 객체의 stopPropagation 메서드는 이벤트 전파를 중지시킴
하위 DOM 요소의 이벤트를 개별적으로 처리하기 위해 이벤트 전파를 중단시킬 때 사용
<!-- 1번은 클릭해도 빨간색으로 변하지 않는다 -->
<!DOCTYPE html>
<html>
<body>
<ul>
<li>1번</li>
<li>2번</li>
<li>3번</li>
</ul>
<script>
const table = document.querySelector('ul');
table.addEventListener('click',e=>{
if (! e.target.matches('ul > li')) return
e.target.style.color = 'red';
});
table.firstElementChild.addEventListener('click', e => e.stopPropagation());
</script>
</body>
</html>
일반 함수로 호출되기 때문에 this는 전역 객체
어트리뷰트 값에 함수 호출문 할당할 때 인수로 this를 전달하면 이벤트를 바인딩한 DOM 요소를 가르킴
<!DOCTYPE html>
<html>
<body>
<button onclick="handleClick(this)">버튼</button>
<script>
function handleClick(that){
console.log(this); // window
console.log(that); // <button>
}
</script>
</body>
</html>
이벤트 핸들러 내부의 this = 이벤트를 바인딩한 DOM 요소
이벤트 핸들러 내부의 this = 이벤트 객체의 currentTarget 프로퍼티
<!DOCTYPE html>
<html>
<body>
<button class="btn1">버튼1</button>
<button class="btn2">버튼2</button>
<button class="btn3">버튼3</button>
<script>
const $btn1 = document.querySelector('button[class="btn1"]');
const $btn2 = document.querySelector('button[class="btn2"]');
const $btn3 = document.querySelector('button[class="btn3"]');
$btn1.onclick = function(e){
console.log(this);
console.log(e.currentTarget === this); // true
}
$btn2.addEventListener('click',function(e){
console.log(this);
console.log(e.currentTarget === this); // true
});
$btn3.addEventListener('click', e => {
console.log(this); // 화살표 함수는 this바인딩 X, 상위 스코프의 this 참조
console.log(e.currentTarget === this); // false
});
</script>
</body>
</html>
이벤트 핸들러 어트리뷰트 방식은 함수 호출문을 할당하기 때문에 인수 전달 가능
<!DOCTYPE html>
<html>
<body>
<button type="text" onclick="handleClick(2,4)">버튼</button>
<script>
const handleClick = (a,b) => alert(`${a}+${b} = ${a+b}`);
</script>
</body>
</html>
이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식의 경우 함수 자체를 할당하기 때문에 인수 전달이 불가능 할 것 같지만
아래와 같이 인수 전달이 가능함
<!DOCTYPE html>
<html>
<body>
<button type="text">버튼</button>
<script>
const $btn = document.querySelector('button');
const handleClick = (a,b) => alert(`${a}+${b} = ${a+b}`);
$btn.addEventListener('click',()=>{
handleClick(2,4);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<button type="text">버튼</button>
<script>
const $btn = document.querySelector('button');
const 이벤트핸들러뱉는함수 = (a,b) => () => alert(`${a}+${b} = ${a+b}`)
$btn.addEventListener('click', 이벤트핸들러뱉는함수(2,4));
</script>
</body>
</html>
const keyboardEvent = new KeyboardEvent('keyup');
const customEvent = new CustomEvent('myType');
console.log(customEvent.bubbles); // false
console.log(customEvent.cancelable); // false
// 이벤트 타입의 따른 고유 프로퍼티를 지정하려면 3번째 인수로 객체를 넘긴다
const customMouseEvent = new MouseEvent('click',{
bubbles : true,
cancelable : true,
clientX : 50,
clientY : 100
});
<!DOCTYPE html>
<html>
<body>
<button type="text">버튼</button>
<script>
const $btn = document.querySelector('button');
$btn.addEventListener('myType', e => {
alert(e.detail.message);
});
const customEvent = new CustomEvent('myType',{
detail : {message : 'Hi'}
});
$btn.dispatchEvent(customEvent);
</script>
</body>
</html>