이벤트 전파
- 만약 비디오 태그를 클릭하게 된 경우, 이벤트 버블링과 이벤트 캡처링이 나타남
- 이 때 이벤트 버블링의 경우, video 태그를 클릭했어도 그 상위 태그의 해당 이벤트를 전파하는 것을 말함
- 이벤트 캡처링의 경우, html, body 등 상위 태그의 이벤트를 클릭 했을 때 하위 태그들에 이벤트가 전파되는 것을 의미함
- 이렇게 이벤트를 디스패칭 하는 것을 이벤트 전파라고 함
- JS 이벤트는 전파됨을 알고, 위에서 아래로 내려가는 캡쳐링, 아래에서 위로 가는 버블링이 있음을 이해하면 됨
이벤트 버블링
- 이벤트 버블링은 계속 상위 요소로 이벤트가 전파되는 것을 의미함
<article>
ARTICLE
<div>
DIV
<p>
P
<br />
<button>BUTTON</button>
</p>
</div>
</article>
const article = document.querySelector('article');
const division = document.querySelector('div');
const paragraph = document.querySelector('p');
const button = document.querySelector('button');
article.addEventListener('click', () => window.alert('article'));
division.addEventListener('click', () => window.alert('division'));
paragraph.addEventListener('click', () => window.alert('paragraph'));
button.addEventListener('click', () => window.alert('button'));
- 위의 경우 계속 이벤트 버블링이 생기는 것, 이를 아래와 같이 stopPropagation을 하면 상위 요소로 이벤트 버블링이 일어나지 않음
const article = document.querySelector('article');
const division = document.querySelector('div');
const paragraph = document.querySelector('p');
const button = document.querySelector('button');
article.addEventListener('click', () => window.alert('article'));
division.addEventListener('click', () => window.alert('division'));
paragraph.addEventListener('click', () => window.alert('paragraph'));
button.addEventListener('click', (event) => {
event.stopPropagation();
window.alert('button');
});
- 다른 프레임워크 사용에서도 이벤트 버블링이 나타나기 떄문에 수정을 해야하는 케이스가 충분히 나올 수 있음
이벤트 캡처링
- 이벤트 캡처링은 상위에서 하위 요소로 내려가는 전파 방법임, 예시를 통해 볼 수 있음
<div>
<h1>장바구니</h1>
<ul>
<li>노트북</li>
<li>모니터</li>
<li>책상</li>
<li>의자</li>
<li>키보드</li>
<li>마우스</li>
</ul>
</div>
const container = document.querySelector('div');
const list = document.querySelector('ul');
container.addEventListener('click', (event) => {
console.error('div에 이벤트 발생');
event.target.style.background = 'blue';
});
list.addEventListener('cilck', (event) => {
console.warn('UL 이벤트 발생');
event.target.style.background = 'red';
});
- DIV 태그 클릭시에도 UL에도 영향을 끼침, 이에 대해서 event 인자를 통해서 캡처링을 조절할 수 있음, 캡처링 여부를 넘기는 것
const container = document.querySelector('div');
const list = document.querySelector('ul');
container.addEventListener(
'click',
(event) => {
console.error('div에 이벤트 발생');
event.target.style.background = 'blue';
},
true,
);
list.addEventListener('cilck', (event) => {
console.warn('UL 이벤트 발생');
event.target.style.background = 'red';
});
container.addEventListener(
'click',
(event) => {
console.error('div에 이벤트 발생');
event.target.style.background = 'blue';
},
{
capture: true,
once: true,
passive: true
},
);
- 내 예상과 다르게 이벤트가 발생되고 처리하는 상황에 대해서 이벤트 캡처링과 버블링으로 인해서 일어나는 것일 수 있음
이벤트 위임
- 아래와 같이 이벤트를 달 때 반복문을 통해서 이벤트를 일일이 다는 경우가 있음
<div>
<h1>장바구니</h1>
<ul>
<li>노트북</li>
<li>모니터</li>
<li>책상</li>
<li>의자</li>
<li>키보드</li>
<li>마우스</li>
</ul>
</div>
const items = document.querySelectorAll('li');
items.forEach((item) => item.addEventListener('click', console.log));
- 하지만 위와 같이 처리하게 되면 li 태그가 늘어나는만큼 이벤트도 늘어나고 그만큼 메모리도 더 차지하고 비효율적임, 이럴 때 이벤트 위임을 쓸 수 있음
- 위의 예시의 경우 li 태그 상위 태그가 ul이므로 ul 태그에 일단 이벤트를 주는 것임, 그렇게 해도 하위 태그까지 이벤트가 전파가 되기 때문에 문제가 없음
const items = document.querySelectorAll('ul');
items.addEventListener('click', console.log);
- 그 외에 class를 등록하는 방법도 있음, 혹은 event의 target의 nodename을 지정해서 할 수도 있음
- 이 경우 ul 태그 내부의 이벤트에 li도 있기에 그렇게 nodename을 지정해서 할 수 있는 것임
const items = document.querySelectorAll('ul');
items.addEventListener('click', (event) => {
if (event.target.nodeName === 'LI') {
alert('LI Click');
} else {
alert('No LI Click');
}
});
item.addEventListener('click', (event) => {
if (event.target.nodeName !== 'LI') {
return;
}
event.target.style.background = 'blue';
});
- 하나의 이벤트를 아주 쉽게 재활용을 할 수 있음(target을 활용해서)
- 그리고 클래스명으로 분기를 할수도 있음, 이 때 특정 클래스명이 있는지 확인해서 처리할수도 있음
<div>
<h1>장바구니</h1>
<ul>
<li class="item">노트북</li>
<li class="item">모니터</li>
<li class="item">책상</li>
<li class="item">의자</li>
<li class="item">키보드</li>
<li class="item">마우스</li>
</ul>
</div>
item.addEventListener('click', (event) => {
const [item] = event.target.classList;
if (item === 'item') {
event.target.style.background = 'blue';
}
});
- 여러개 요소의 일일이 이벤트를 등록하지 않고 이벤트를 위임해서 관리하기 수월하게 쓸 수 있음
debounce
- 브라우저에서 검색창 입력할때마다 주기적으로 키 입력시 계속해서 서버를 호출함, 그나마 현재 상용브라우저는 잘 처리되어 있는 것임
- 아래 예시로 보면 input에 값을 입력할 때마다 이벤트가 호출이 계속됨, 이를 API 호출이라고 하면 자원 낭비가 아주 심한 것임, 이럴 때 debounce 처리를 해 볼 수 있음
<form>
<input type="text"/>
</form>
let i = 0;
document.querySelector('input').addEventListener('keyup', () => {
i = i + 1;
console.log(i);
});
function debounce(callback, wait) {
let timeout;
return function (...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => callback.apply(context, args), wait);
};
}
document.querySelector('input').addEventListener(
'keyup',
debounce(() => {
i = i + 1;
console.log(i);
}, 1000),
);
- 위처럼 처리하면 입력한 시간대로 이벤트 처리를 함, 그래서 불필요한 호출을 상당히 개선하고 최적화를 할 수 있음
- debounce는 이벤트 발생이 많을 때 가장 마지막 이벤트만을 실행시킴, 이 실행시키는 시점은 밀리세컨드로 좌우하는 것임
- 언제 사용해야 할 지 생각해봐야함
throttle
- 과거에는 싱글 페이지 어플리케이션이 없을 땐, 페이징 형태의 UI가 많았음, 보조적인 페이지 혹은 더보기 버튼으로 필요한 내용을 더 보는 것
- 하지만 요즘에는, 무한 스크롤해서 계속 아이템이 등장하는 로직과 매커니즘이 생겨남
- 이럴 때 throttle을 쓰면 좋음
<div style="background-color: red"><h2>1</h2></div>
<div style="background-color: orange"><h2>2</h2></div>
<div style="background-color: yellow"><h2>3</h2></div>
<div style="background-color: green"><h2>4</h2></div>
<div style="background-color: blue"><h2>5</h2></div>
<div style="background-color: navy"><h2>6</h2></div>
<div style="background-color: violet"><h2>7</h2></div>
- 이벤트 처리는 유사하나 window에 scroll을 달았음, 아래의 경우 스크롤 할 때마다 계산을 함 이를 최적화하는데 throttle이 유용함
let i = 0;
window.addEventListener('scroll', () => {
i = i + 1;
console.log(i);
});
function throttle(callback, wait) {
let timeout = null;
return function (...args) {
const context = this;
if (!timeout) {
timeout = setTimeout(() => {
callback.apply(this, args);
timeout = null;
}, wait);
}
};
}
let i = 0;
window.addEventListener(
'scroll',
throttle(() => {
i = i + 1;
console.log(i);
}, 1000),
);