유데미 강의 JavaScript 완벽 가이드 : 초급 + 고급 마스터 과정을 들으면서 자바스크립트를 다시 한 번 익히는 중이다. 계속되는 포스팅에서 강의를 듣고 미처 몰랐던 부분, 심화 이해가 필요한 부분에 대해 주제별로 간단하게 기록하겠다.
const onClickBtn = () => {};
btn.addEventListener('click', onClickBtn);
btn.removeEventListener('click', onClickBtn);
// remove되지 않는다.
btn.addEventListener('click', onClickBtn.bind(this));
btn.removeEventListener('click', onClickBtn.bind(this));
// 해결
const boundFn = onClickBtn.bind(this);
btn.addEventListener('click', boundFn);
btn.removeEventListener('click', boundFn);
캡처링 단계: 최상위 요소(보통 document 혹은 window 객체)에서 시작하여 실제 이벤트가 발생한 요소까지 내려가면서 이벤트가 발생한 요소를 추적하는 단계. 이 단계에서 이벤트 리스너가 동작하게 하려면, addEventListener의 세 번째 인자를 true로 설정해야 한다.
타깃 단계 : 이벤트가 실제로 발생한 요소에 도달하면 해당 요소에 등록된 이벤트 리스너가 동작한다.
버블링 단계: 이벤트는 발생한 요소에서 다시 최상위 요소로 '올라간다'. 이 단계에서 이벤트 리스너가 동작하게 하려면, addEventListener의 세 번째 인자를 false로 설정하거나 생략한다.
btn.addEventListener('click', () => {
event.stopImmediatePropagation();
})
btn.addEventListener('click', handler1) // 실행안됨
btn.addEventListener('click', handler2) // 실행안됨
상위 요소에 이벤트 리스너를 달아 하위 요소에서 발생하는 이벤트를 감지하는 것.
// 나이브한 이벤트리스너 사용
const listItems = document.querySelectorAll('li');
listItems.forEach(item => {
item.addEventListener('click', e => {
e.target.classList.toggle('hightlight')
})
})
const list = document.querySelector('ul');
list.addEventListener('click', e => {
// currentTarget은 이벤트 리스너가 부착된 요소
// e.target은 클릭되어진 실제 목표를 참조, 단 이벤트 위임은 복잡한 구조에서는 문제가 생길 수 있음
// e.target.classList.toggle('hightlight')
// 완벽한 이벤트 위임 패턴, 이벤트를 위임하되 DOM 탐색을 조합
e.target.closest('li').classList.toggle('hightlight');
})
// html 설정
<li id="p3" draggable="true">
connectDrag() {
const item = document.getElementById(this.id);
item.addEventListener('dragstart', (e) => {
// 드롭시 (dragenter, dragover) 'text/plain'으로 설정한 데이터만 받을 수 있도록
e.dataTransfer.setData('text/plain', this.id);
e.dataTransfer.effectAllowed = 'move';
const clone = item.cloneNode(true);
clone.style.position = 'absolute';
clone.style.width = '300px';
clone.style.top = '-9999px';
document.body.appendChild(clone);
e.dataTransfer.setDragImage(clone, 0, 0);
setTimeout(() => document.body.removeChild(clone), 0);
});
}
connectDroppable() {
const listEl = document.querySelector(`#${this.type}-projects ul`);
// dragenter : 드래그 중인 요소가 드롭 대상 요소의 범위로 처음 진입할 때 한 번만 발생
// dragover : 드래그 중인 요소가 드롭 대상 요소의 범위 위에서 마우스를 움직일 때마다 계속해서 발생
listEl.addEventListener('dragenter', (e) => {
if (e.dataTransfer.types[0] === 'text/plain') {
listEl.parentElement.classList.add('droppable');
e.preventDefault();
}
});
listEl.addEventListener('dragover', (e) => {
// console.log(e.dataTransfer);
if (e.dataTransfer.types[0] === 'text/plain') {
e.preventDefault();
}
});
listEl.addEventListener('dragleave', (e) => {
if (e.relatedTarget.closest(`#${this.type}-projects ul`) !== listEl) {
listEl.parentElement.classList.remove('droppable');
}
});
listEl.addEventListener('drop', (e) => {
const dropElId = e.dataTransfer.getData('text/plain');
if (this.projects.find((p) => p.id === dropElId)) {
return;
}
document
.getElementById(dropElId)
.querySelector('button:last-of-type')
.click();
});
listEl.addEventListener('dragend', (e) => {
listEl.parentElement.classList.remove('droppable');
// e.preventDefault(); // not required
});
}