2024. 1. 7. 15:41ㆍ개인노트-개인공부
목차
- 이벤트 추가 및 제거
- 이벤트 객체
- 기본 동작 방지
- 버블링과 캡처링
- 이벤트 옵션
- 이벤트 위임
- 키보드 이벤트
- 양식과 포커스 이벤트
- 커스텀 이벤트와 디스패치
# 이벤트 추가 및 제거
- .addEventListener()로 이벤트를 등록합니다.
- 대상에 이벤트 청취(Listen)를 등록합니다.
- 대상에 지정한 이벤트가 발생했을 때 지정한 함수(Handler)가 호출됩니다.
- .removeEventListener()로 등록된 이벤트를 제거합니다.
- 대상에 등록했던 이벤트 청취(Listen)를 제거합니다.
- 메모리 관리를 위해 등록한 이벤트를 제거하는 과정이 필요할 수 있습니다.
## 예시 코드
<!-- index.html -->
<body>
<div class='parent'>
<div class='child'>
<a href='https://www.naver.com' target='_blank'>
click
</a>
</div>
</div>
</body>
### .addEventListener()의 예시코드
// main.js
// .addEventListener()
// 대상에 이벤트 청취(Listen)를 등록합니다.
// 대상에 지정한 이벤트가 발생했을 때 지정한 함수(Handler)가 호출됩니다.
const parentEl = document.querySelector('.parent');
const childEl = document.querySelector('.child');
parentEl.addEventListener('click', () => {
console.log('Parent!');
});
childEl.addEventListener('click', () => {
console.log('Child!');
});
클릭 결과
parent엘리먼트를 클릭하면 'Parent!'가 출력되고,
child엘리먼트를 클릭하면 'Child!', 'Parent!' 2개가 출력됩니다. child 엘리먼트의 영역은 child 영역이면서 parent영역이기도 하기 때문입니다.
### .removeEventListener()의 예시코드
// main.js
// .removeEventListener()
// 대상에 등록했던 이벤트 청취(Listen)를 제거합니다.
// 메모리 관리를 위해 등록한 이벤트를 제거하는 과정이 필요할 수 있습니다.
const parentEl = document.querySelector('.parent');
const childEl = document.querySelector('.child');
const handler = () => {
console.log('Parent!');
};
parentEl.addEventListener('click', handler);
childEl.addEventListener('click', () => {
parentEl.removeEventListener('click', handler);
});
## 정리
- .addEventListener()로 이벤트를 등록합니다.
- .removeEventListener()로 등록된 이벤트를 제거합니다.
- child 요소를 '클릭'하면 그 조상인 parent의 '클릭' 이벤트도 동작합니다.
# 이벤트 객체
- 이벤트 객체는 대상에서 발생한 이벤트 정보를 담고 있습니다.
- 이벤트 리스너의 콜백함수 매개변수로 이벤트 객체가 전달됩니다.
## 예시 코드
// main.js
// 이벤트 객체
// 이벤트 객체는 대상에서 발생한 이벤트 정보를 담고 있습니다.
const parentEl = document.querySelector('.parent');
parentEl.addEventListener('click', event => {
console.log(event.target, event.currentTarget);
});
childEl.addEventListener('wheel', event => {
console.log(event);
});
클릭 결과
console.log(event.target, event.currentTarget)의 결과


target : 이벤트가 발생한 해당 요소
currentTarget : 이벤트가 등록된 요소
## 정리
- 이벤트 객체에서는 대상에서 발생한 다양한 이벤트 정보를 담고 있습니다.
# 기본 동작 방지
- event.preventDefault() : 이벤트의 기본 동작을 막을 수 있습니다.
- 예 : a 태그에서의 페이지 이동
## 예시 코드
// main.js
// 마우스 휠의 스크롤 동작 방지!
const parentEl = document.querySelector('.parent');
parentEl.addEventListener('wheel', event => {
event.preventDefault();
console.log('Wheel!');
// console.log에 'Wheel!' 이라는 문자는 나오지만, 화면에서 Wheel은 동작하지 않습니다.
});
// <a> 태그에서 페이지 이동 방지!
const anchorEl = document.querySelector('a');
childEl.addEventListener('click', event => {
event.preventDefault();
console.log('Click!');
// console.log에 'Click!' 이라는 문자는 나오지만, 페이지가 이동되지는 않습니다.
});
## 정리
- 이벤트의 기본 동작이 필요하지 않는 경우가 있을 수 있습니다. 그런 경우 이벤트 콜백 메소드 안에 event.preventDefault()함수를 호출하여 이벤트의 기본 동작을 막을 수 있습니다.
# 버블링과 캡쳐링
- 캡쳐링 : 이벤트가 하위 요소로 전파되는 단계.
- addEventListener의 세번째 인수로 {capture: true}를 적용하면 그 이벤트는 버블링 단계가 아닌 캡쳐링 단계에서 동작합니다.
- 버블링 : 이벤트 버블링은 타깃 이벤트에서 시작해서 <html> 요소를 거쳐 document 객체를 만날 때까지 상위요소로 전파되어 각 노드에서 모두 이벤트가 발생하는 현상입니다.
- event.stopPropagation()으로 버블링을 정지 시킬 수 있습니다.
## 예시 코드
### 버블링 실습 예시
<!-- index.html -->
<body>
<div class='parent'>
<div class='child'>
<a href='https://www.naver.com' target='_blank'>
click
</a>
</div>
</div>
</body>
// main.js
// 이벤트 전파(버블) 정지
const parentEl = document.querySelector('.parent');
const childEl = document.querySelector('.child');
const anchorEl = document.querySelector('a');
window.addEventListener('click', event => {
console.log('Window!');
});
document.body.addEventListener('click', event => {
console.log('Body!');
});
parentEl.addEventListener('click', event => {
console.log('Parent!');
event.stopPropagation(); // 버블링 정지!
});
childEl.addEventListener('click', event => {
console.log('Child!');
});
anchorEl.addEventListener('click', event => {
console.log('Anchor!');
});
이벤트 버블링


### 캡쳐링 실습 예시
// main.js
// 캡쳐링
// addEventListener의 세번째 인수로 {capture: true}를 전달합니다.
// {capture: true}이면 그 이벤트는 캡쳐링 단계에서 동작합니다.
const parentEl = document.querySelector('.parent');
const childEl = document.querySelector('.child');
const anchorEl = document.querySelector('a');
window.addEventListener('click', event => {
console.log('Window!');
});
document.body.addEventListener('click', event => {
console.log('Body!');
}, {capture: true});
parentEl.addEventListener('click', event => {
console.log('Parent!');
});
childEl.addEventListener('click', event => {
console.log('Child!');
});
anchorEl.addEventListener('click', event => {
console.log('Anchor!');
});
이벤트 캡쳐링
{capture: true} 가 적용된 EventListener는 버블링 단계에서 동작하지 않고, 캡쳐링 단계에서 동작합니다.
이벤트 동작 순서 : 상위(window)에서 하위요소(타겟)로..(캡쳐링) -> 이벤트 타겟 -> 하위요소(타겟)에서 상위(window)로..(버블링)

## 정리
- 더 자세한 캡쳐링과 버블링에 대한 정보
# 이벤트 옵션
## once: true
- { once: true } : 이벤트가 단 한번만 동작합니다.
// 핸들러를 한 번만 실행
const parentEl = document.querySelector('.parent');
parentEl.addEventListener('click', event => {
console.log('Parent!');
}, {
once: true
});
## passive: true
- { passive: true } : touch, wheel 등 일부 이벤트에서 동작을 최적화하여 스크롤 성능을 대폭 향상시킬 수 있는 웹 표준 기능입니다.
// 기본 동작과 핸들러 실행 분리
const parentEl = document.querySelector('.parent');
parentEl.addEventListener('sheel', event => {
for(let i = 0; i < 10000; i += 1) {
console.log(i);
}
}, {
passive: true
});
위의 for 문의 실행으로 인해서 화면에서의 wheel 동작이 버벅일 수 있습니다. passive: true로 화면이 버벅이는 현상을 줄일 수 있습니다.
# 이벤트 위임
- 이벤트 위임(Delegation)
- 비슷한 패턴의 여러 요소에서 이벤트를 핸들링해야 하는 경우,
- 단일 조상 요소에서 제어하는 이벤트 위임 패턴을 사용할 수 있습니다.
## 예시 코드
### 이벤트 위임없이 이벤트 등록
아래 코드는 child에 총 4번의 이벤트를 각각 등록하여 사용합니다.
<!-- index.html -->
<!-- ... -->
<body>
<div class='parent'>
<div class='child'>1</div>
<div class='child'>2</div>
<div class='child'>3</div>
<div class='child'>4</div>
</div>
</body>
<!-- ... -->
// main.js
const parentEl = document.querySelector('.parent');
const childEls = document.querySelectorAll('.child');
// 모든 대상 요소에 이벤트 등록
childEls.forEach(el => {
el.addEventListener('click', event => {
console.log(event.target.textContent);
});
});
### 이벤트 위임을 사용한 패턴
// main.js
const parentEl = document.querySelector('.parent');
const childEls = document.querySelectorAll('.child');
// 조상 요소에 이벤트 위임
parentEl.addEventListener('click', event => {
const childEl = event.target.closest('.child');
if (childEl) {
console.log(childEl.textContent);
}
});
target : 실제로 이벤트가 발생한 요소.
closest 메소드 : 대상 요소의 선택자와 일치하는 (대상요소를 포함한)가장 가까운 조상요소를 찾습니다.
## 정리
- 엘리먼트가 많아지면 많아질 수록 이벤트 핸들러를 등록하는 것을 절약할 수 있습니다.
- 코드를 한 번만 작성하기 때문에 효율적으로 어플리케이션을 관리할 수 있습니다.
# 키보드 이벤트
- 키보드로 한글을 입력하면 브라우저는 입력된 한글을 처리하는 과정이 필요합니다.
- 특히, input요소에서 엔터키나 탭키 처럼 입력을 완료하는 키를 누르거나 혹은 keydown, keyup의 이벤트를 발생시키면 그 이벤트가 두 번 처리되는 현상이 나타납니다.
- 이는 한글 뿐만이 아니라 중국어나 일본어에서도 발생합니다.
- 이러한 CJK문자(한글,중국,일본어)는 브라우저에서 처리하는 과정이 한 단계가 더 필요하기 때문에 두 번의 결과가 출력됩니다.
- 이런 중간 처리과정을 event.isComposing 을 통하여 알 수 있습니다.

## 예시 코드
// main.js
// Keyboard Events
// keydown : 키를 누를 때
// keyup : 키를 땔 때
const inputEl = document.querySelector('input');
inputEl.addEventListener('keydown', event => {
if (event.key === 'Enter') {
console.log(event.isComposing);
console.log(event.target.value); // 한글을 입력하면 두 번 처리됩니다.
}
});

// 더 나은 코드
inputEl.addEventListener('keydown', event => {
if (event.key === 'Enter' && !event.isComposing) {
console.log(event.isComposing);
console.log(event.target.value); // 이제 한글을 입력해도 한 번만 처리 됩니다.
}
});

## 정리
- 키보드 이벤트를 다룰 때, 엔터나 탭키처럼 입력을 완료하는 코드과 한글 입력을 같이 사용한다면, 이벤트의 isComposing 속성을 같이 로직으로 처리하는 것이 필요합니다.
# 양식과 포커스 이벤트
- focus : 요소가 포커스를 얻었을 때
- blur : 요소가 포커스를 잃었을 때
- input : 값이 변경되었을 때
- change : 상태가 변경되었을 때
- submit : 제출 버튼을 선택했을 때
- reset : 리셋 버튼을 선택했을 때
## 예시 코드
<!-- ... -->
<style>
form {
padding: 10px;
border: 4px solid transparent;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
form.active {
border-color: orange;
}
</style>
</head>
<body>
<form>
<input type="text" placeholder="ID" />
<input type="password" placeholder="PW" />
<button type="submit">제출</button>
<button type="reset">초기화</button>
</form>
</body>
<!-- ... -->
// main.js
// Focus & Form Events
const formEl = document.querySelector('form');
const inputEls = document.querySelectorAll('input');
inputEls.forEach(el => {
el.addEventListener('focus', () => {
formEl.classList.add('active');
});
el.addEventListener('blur', () => {
formEl.classList.remove('active');
});
el.addEventListener('change', event => {
console.log(event.target.value);
});
});
formEl.addEventListener('submit', event => {
event.preventDefault();
const data = {
id: event.target[0].value,
pw: event.target[1].value,
}
console.log('제출!', data);
});
formEl.addEventListener('reset', event => {
console.log('리셋!');
});

## 정리
- 이벤트
- input : 값이 변경되었을 때(키보드를 입력할 때마다 동작합니다.)
- change : 상태가 변경되었을 때(모든 값 입력을 끝내고 다른 요소로 포커스를 이동했을 때 동작합니다). 즉, 엘리먼트의 상태가 변경되었을 때 동작합니다.
- 제출 버튼(type='submit'인 버튼)을 클릭하면 form에 submit이라는 이벤트가 발생합니다.
- form요소에서는 submit 이벤트가 발생하면 페이작 새로고침되는 것이 기본동작입니다.
- 페이지를 새로고침하는 기본 동작을 막기위해 event.preventDefault();를 사용합니다.
# 커스텀 이벤트와 디스패치
디스패치
- 화면에서 실제로 이벤트를 발생시키지 않아도 dispatchEvent 메소드를 사용하여 강제로 이벤트를 발생시킬 수 있습니다.
- 인수로는 이벤트 인스턴스를 넘겨줍니다. 예 : new Event('click')
커스텀 이벤트
- javascript에는 존재하지 않는 이벤트라도 dispatchEvent 메소드를 사용하면 이벤트를 강제로 발생시킬 수 있습니다.
## 예시 코드
### 디스패치 예시
<body>
<div class="parent">
<div class="child">1</div>
<div class="child">2</div>
</div>
</body>
// 디스패치
const child1 = document.querySelector('.child:nth-child(1)');
const child2 = document.querySelector('.child:nth-child(2)');
child1.addEventListener('click', event => {
// 강제로 이벤트 발생!
child2.dispatchEvent(new Event('click'));
child2.dispatchEvent(new Event('wheel'));
child2.dispatchEvent(new Event('keydown'));
});
child2.addEventListener('click', event => {
console.log('Child2 Click!');
});
child2.addEventListener('wheel', event => {
console.log('Child2 Wheel!');
});
child2.addEventListener('keydown', event => {
console.log('Child2 Keydown!');
});
결과

### 커스텀 이벤트 예시
// 커스텀 이벤트
const child1 = document.querySelector('.child:nth-child(1)');
const child2 = document.querySelector('.child:nth-child(2)');
// addEventListener에 javascript에는 존재하지 않는 'hello-world' 라는 커스텀 이벤트를 연결했습니다.
// 당연하게도 hello-world 이벤트는 어떠한 경우에도 발생하지 않습니다.
child1.addEventListener('hello-world', event => {
console.log('커스텀 이벤트 발생!');
console.log(event.detail);
});
// 하지만, dispatchEvent를 이용하여 이벤트를 강제로 발생시키면
// 임의의 이름으로 만든 이벤트를 동작시킬 수 있습니다.
child2.addEventListener('click', () => {
child1.dispatchEvent(new Event('hello-world'));
});
결과

참고로 event 객체에는 detail이라는 속성이 없으므로 undefined가 출력됩니다.
### 커스텀 이벤트 예시2
Event 생성자 함수 뿐만 아니라 CustomEvent 생성자 함수라는 것도 존재합니다.
// 커스텀 이벤트
const child1 = document.querySelector('.child:nth-child(1)');
const child2 = document.querySelector('.child:nth-child(2)');
child1.addEventListener('hello-world', event => {
console.log('커스텀 이벤트 발생!');
console.log(event.detail);
});
// Event 대신 CustomEvent 생성자 함수를 사용하고, 두번째 인수로 객체를 넘겨줍니다.
//
child2.addEventListener('click', () => {
child1.dispatchEvent(new CustomEvent('hello-world'), {detail: 123});
});
결과

javascript에 존재하지 않는 custom한 이벤트를 만들 수도 있고 실행할 수도 있는데, 만약 실행 할 때 어떤 데이터를 같이 전달하고 싶으면 두번째 인수로 detail 속성에 데이터를 추가하고 객체로 넘겨주면 됩니다.
detail 속성을 이용하여 데이터를 넘겨주고 싶다면 Event 생성자 함수가 아닌 CustomEvent 생성자 함수를 사용해야 합니다.
'개인노트-개인공부' 카테고리의 다른 글
Node vs Element (2) | 2024.01.28 |
---|---|
javascript - 클로저 (1) | 2024.01.21 |
TailWindCSS (2) | 2024.01.02 |
Vite란? (1) | 2023.12.25 |
Zustand (2) | 2023.12.17 |