● 이벤트전파
캡쳐링 단계 : 이벤트 객체가 생성되면 window부터 시작해서 이벤트 타깃방향으로 전파된다
타깃 단계 : 이벤트 객체가 이벤트를 발생시킨 이벤트 타깃에 도달함
버블링 단계 : 다시 이벤트 타깃부터 window 방향으로 전파
<body>
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
<script>
const $fruits = document.getElementById("fruits");
$fruits.addEventListener("click", function (e) {
console.log(e.eventPhase);
console.log(e.target);
console.log(e.currentTarget);
});
</script>
</body>
두 번째, banana를 클릭하면 아래와 같이 출력된다.
해석을 해보자면.. 일단 addEventListener는 타깃단계와 버블링 단계를 캐치할 수 있기 때문에, banana를 누르니까 버블링을 통해 위의 이벤트 핸들러가 실행되는 것이다. 그리고 currentTarget은 이벤트 핸들러를 호출한 이벤트 타깃을 가르키고, target은 이벤트를 발생시킨, 즉 마우스 커서가 누른 타깃을 가르킨다.
// 3
// <li class="banana">Banana</li>
// <ul id="fruits">
// <li class="apple">Apple</li>
// <li class="banana">Banana</li>
// <li class="orange">Orange</li>
// </ul>
대부분의 이벤트는 버블링을 통해 전파된다. 하지만 다음 이벤트는 버블링을 통해 전파되지 않는다. 아래의 이벤트들은 event.bubble 프로퍼티가 모두 false 이다.
- 포커스 이벤트 : focus / blur
- 리소스 이벤트 : load / unload /abort / error
- 마우스 이벤트 : mouseenter / mouseleave
+ 캡처링 단계에서 이벤트를 캐치해야할 경우는 거의 없다.
● 이벤트위임
다수의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하지 말고 그 요소들을 포함하는 상위 DOM 요소에 이벤트 핸들러를 딱 한번 등록하는 것이다.
- 주의사항
- 내가 원하지 않았던 요소에서 이벤트가 발생할 수도 있음
- 여러개의 li태그에서의 click이벤트를 처리하기위해 ul태그에 이벤트를 위임 했을 때 li태그의 영역이 아닌 ul태그의 영역에서 click하게되면 원하지 않는 상황이 발생한다.
- 따라서 이벤트 핸들러에서 이벤트가 발생한 요소를 한번 더 체크해야한다.
<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>
⚬ DOM 요소의 기본 동작 중단
이벤트 객체의 preventDefault 메서드는 DOM 요소의 기본 동작을 중단시킴
<!-- <a> 클릭해도 네이버 홈페이지로 이동하지 않음 -->
<body>
<a href="https://www.naver.com">Naver</a>
<script>
const atag = document.querySelector('a');
atag.addEventListener('click', e => e.preventDefault());
</script>
</body>
⚬ 이벤트 전파 방지
이벤트 객체의 stopPropagation 메서드는 이벤트 전파를 중지시킴
하위 DOM 요소의 이벤트를 개별적으로 처리하기 위해 이벤트 전파를 중단시킬 때 사용
<!-- 1번은 클릭해도 빨간색으로 변하지 않는다 -->
<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>
● 이벤트 핸들러 내부의 this
⚬ 이벤트 핸들러 어트리뷰트 방식
- 일반 함수로 호출되기 때문에 this는 전역 객체
- 어트리뷰트 값에 함수 호출문 할당할 때 인수로 this를 전달하면 이벤트를 바인딩한 DOM 요소를 가르킴
<body>
<button onclick="handleClick()">버튼</button>
<script>
function handleClick(){
console.log(this); // window
}
</script>
</body>
<body>
<button onclick="handleClick(this)">버튼</button>
<script>
function handleClick(button) {
console.log(button); // 이벤트를 바인딩한 요소
console.log(this); // window
}
</script>
</body>
⚬ 이벤트 핸들러 프로퍼티 방식, addEventListener
- 이벤트 핸들러 내부의 this = 이벤트를 바인딩한 DOM 요소
- 이벤트 핸들러 내부의 this = 이벤트 객체의 currentTarget 프로퍼티
<body>
<button class="btn1">버튼1</button>
<button class="btn2">버튼2</button>
<button class="btn3">버튼3</button>
<script>
const $btn1 = document.querySelector('.btn1');
const $btn2 = document.querySelector('.btn2');
const $btn3 = document.querySelector('.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>
● 이벤트 핸들러에 인수 전달
이벤트 핸들러 어트리뷰트 방식은 함수 호출문을 할당하기 때문에 인수 전달 가능하다.
<body>
<button type="text" onclick="handleClick(2,4)">버튼</button>
<script>
const handleClick = (a,b) => alert(`${a}+${b} = ${a+b}`);
</script>
</body>
이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식의 경우 함수 자체를 할당하기 때문에 인수 전달이 불가능 할 것 같지만
아래와 같이 인수 전달이 가능함
- 이벤트 핸들러 내부에서 함수를 호출
<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>
- 이벤트 핸들러를 반환하는 함수를 호출
<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>
● 커스텀 이벤트
⚬ 커스텀 이벤트 생성
- 생성자 함수를 호출하여 명시적으로 생성한 이벤트 객체는 이벤트 타입 지정 가능
- 이 때 생성자 함수의 첫 번째 인수는 이벤트 타입을 나타내는 문자열
- 기존의 이벤트 타입이 아니라면 CustomEvent 생성자 함수를 사용
- 커스텀 이벤트 객체는 버블링 X, preventDefault 로 취소 불가
- 두번째 인수를 통해 기존 이벤트 타입의 고유 프로퍼티 지정 가능
- isTrusted 프로퍼티는 언제나 false
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
});
⚬ 커스텀 이벤트 디스패치
- 디스패치 = 이벤트를 발생시키는 행위
- dispatchEvent(이벤트 객체) 메서드로 커스텀 이벤트를 발생시킬 수 있음
- 일반적인 이벤트 핸들러는 비동기 처리 방식이지만 dispatchEvent는 동기
- dispatchEvent 메서드를 호출하면 커스텀 이벤트에 바인딩된 이벤트 핸들러를 직접 호출하는 것이기 때문에 커스텀 이벤트를 처리할 이벤트 핸들러를 등록한 이후 디스패치 해야함
- 커스텀 이벤트 타입은 이벤트 핸들러 등록시 addEventListner 메서드만 가능. 요소 노드에는 당연히 내가 만든 이벤트 타입에 해당하는 프로퍼티가 없으니까
<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>