이벤트 드리븐 프로그래밍
- 브라우저는 처리해야 할 특정 사건이 발생하면 이를 감지하여 이벤트를 발생
- 이벤트 핸들러 : 이벤트가 발생했을 때 호출될 함수
- 이벤트 핸들러 등록 : 이벤트가 발생했을 때 브라우저에게 이벤트 핸들러의 호출을 위임하는 것
- 이벤트 핸들러를 언제 호출할지 알 수 없으므로 브라우저에게 함수 호출을 위임
- 이벤트 드리븐 프로그래밍 : 프로그램의 흐름을 이벤트 중심으로 제어하는 프로그래밍 방식
이벤트 타입
- 이벤트 타입 : 이벤트 종류를 나타내는 문자열
1. 마우스 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| click | 마우스 버튼을 클릭했을 때 |
| dbclick | 마우스 버튼을 더블 클릭했을 때 |
| mousedown | 마우스 버튼을 눌렀을 때 |
| mouseup | 누르고 있던 마우스 버튼을 놓았을 때 |
| mousemove | 마우스 커서를 움직였을 때 |
| mouseenter | 마우스 커서를 HTML 요소 안으로 이동했을 때 (버블링 X) |
| mouseover | 마우스 커서를 HTML 요소 안으로 이동했을 때 (버블링) |
| mouseleave | 마우스 커서를 HTML 요소 밖으로 이동했을 때 (버블링 X) |
| mouseout | 마우스 커서를 HTML 요소 밖으로 이동했을 때 (버블링) |
2. 키보드 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| keydown | 모든 키를 눌렀을 때 발생 |
| keypress | 문자 키를 눌렀을 때 연속적으로 발생 |
| keyup | 누르고 있던 키를 놓았을 때 한 번만 발생 |
3. 포커스 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| focus | HTML 요소가 포커스를 받았을 때 (버블링 X) |
| blur | HTML 요소가 포커스를 잃었을 때 (버블링 X) |
| focusin | HTML 요소가 포커스를 받았을 때 (버블링) |
| focusout | HTML 요소가 포커스를 잃었을 때 (버블링) |
4. 폼 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| submit | - form 요소 내의 input(text, checkbox, radio), select 입력 필드 (textarea 제외) 에서 엔터 키를 눌렀을 때 - form 요소 내의 submit 버튼을 클릭했을 때 |
| reset | form 요소 내의 reset 버튼을 클릭했을 때 |
5. 값 변경 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| input | input(text, checkbox, radio), select, textarea 요소의 값이 입력되었을 때 |
| change | input(text, checkbox, radio), select, textarea 요소의 값이 변경되었을 때 |
| readystatechange | HTML 문서의 로드와 파싱 상태를 나타내는 document.readyState 프로퍼티 값이 변경될 때 |
6. DOM 뮤테이션 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| DOMContentLoaded | HTML 문서의 로드와 파싱이 완료되어 DOM 생성이 완료되었을 때 |
7. 뷰 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| resize | 브라우저 윈도우 크기를 라사이즈할 때 연속적으로 발생 |
| scroll | 웹페이지 또는 HTML 요소를 스크롤할 때 연속적으로 발생 |
8. 리소스 이벤트
| 이벤트 타입 | 이벤트 발생 시점 |
| load | DOMContentLoaded 이벤트가 발생한 이후, 모든 리소스의 로딩이 완료되었을 때 |
| unload | 리소스가 언로드될 때 (주로 새로운 웹페이지를 요청한 경우) |
| abort | 리소스 로딩이 중단되었을 때 |
| error | 리소스 로딩이 실패했을 때 |
이벤트 핸들러 등록
- 이벤트가 발생하면 브라우저에 의해 호출될 함수가 이벤트 핸들러
- 이벤트가 발생했을 때 브라우저에게 이벤트 핸들러의 호출을 위임하는 것이 이벤트 핸들러 등록
1. 이벤트 핸들러 어트리뷰트 방식
- on 접두사와 이벤트의 종류를 나타내는 이벤트 타입으로 이루어짐
- 이벤트 핸들러 어트리뷰트 값으로 함수 호출문 등 문을 할당하면 이벤트 핸들러가 등록됨
- 이벤트 핸들러 어트리뷰트 값으로 함수 참조가 아닌 함수 호출문 등의 문을 할당
- 이벤트 핸들러 어트리뷰트 값은 사실 암묵적으로 생성될 이벤트 핸들러의 몸체 함수를 의미 (이벤트 핸들러에 인수를 전달하기 위해서)
<button onClick="sayHi('Echichi')">Click me</button>
// <button onClick="sayHi">Click me</button> 인수 전달하기 곤란
/*
onClick="sayHi('Echichi')" 어트리뷰트는 파싱되어 다음과 같은 함수를 암묵적으로 생성
이벤트 핸들러 어트리뷰트 이름과 동일한 키 onClick 이벤트 핸들러 프로퍼티에 할당
function onClick(event){
sayHi('Echichi');
}
*/
2. 이벤트 핸들러 프로퍼티 방식
- on 접두사와 이벤트의 종류를 나타내는 이벤트 타입으로 이루어짐
- 이벤트 핸들러 프로퍼티에 함수를 바인딩하면 이벤트 핸들러 등록
// 이벤트 핸들러 프로퍼티에 이벤트 핸들러 바인딩
$button.onClick = function(){
console.log('Button Click!');
}
- 이벤트 핸들러를 등록하기 위해서는 이벤트를 발생시킬 객체인 이벤트 타깃과 이벤트의 종류를 나타내는 문자열인 이벤트 타입 그리고 이벤트 핸들러를 지정할 필요가 있음
- 이벤트 핸들러 프로퍼티에는 하나의 이벤트 핸들러만 바인딩 할 수 있음
3. addEventListner 메서드 방식
- addEventListner 메서드의 첫 번째 매개변수에는 이벤트의 종류를 나타내는 문자열인 이벤트 타입을 전달
- addEventListner 메서드의 두 번째 매개변수에는 이벤트 핸들러 전달
- addEventListner 메서드의 마지막 매개변수에는 이벤트를 캐치할 이벤트 전파 단계 (캡쳐링 또는 버블링)를 지정
/*
이벤트 핸들러 프로퍼티에 이벤트 핸들러 바인딩
$button.onClick = function(){
console.log('Button Click!');
}
*/
// addEventListner 방식
$button.addEventListner('click', function(){
console.log('Button Click!');
})
- 이벤트 핸들러 프로퍼티 방식은 이벤트 핸들러 프로퍼티에 이벤트 핸들러를 바인딩하지만 addEventListner 메서드에는 이벤트 핸들러를 인수로 전달
- 동일한 HTML 요소에서 발생한 동일한 이벤트에 대해 이벤트 핸들러 프로퍼티 방식은 하나 이상의 이벤트 핸들러를 등록할 수 없지만 addEventListner 메서드는 하나 이상의 이벤트 핸들러 등록 가능 (등록된 순서대로 호출)
이벤트 핸들러 제거
- addEventListner 메서드로 등록한 이벤트를 제거하려면 EventTarget.prototype.removeEventListner 사용
- removeEventListner메서드에 전달한 인수는 addEventListner 메서드에서 인수로 전달한 등록 이벤트 핸들러와 동일한 함수여야 제거됨
- 무명 함수를 이벤트 핸들러로 등록한 경우 제거 불가
- 함수 자신을 가리키는 argument.callee 도 있지만 코드 최적화 방해
- 이벤트 핸들러 프로퍼티 방식으로 등록한 이벤트 핸들러를 제거하려면 이벤트 핸들러 프로퍼티에 null 할당
이벤트 객체
- 이벤트가 발생하면 이벤트에 관련한 다양한 정보를 담고 있는 이벤트 객체가 동적으로 생성
- 생성된 이벤트 객체는 이벤트 핸들러의 첫 번째 인수로 전달
- 클릭 이벤트에 의해 생성된 이벤트 객체는 이벤트 핸들러의 첫 번째 인수로 전달되어 매개변수 e 에 암묵적으로 할당 (브라우저가 이벤트 핸들러를 호출할 때 이벤트 객체를 인수로 전달하기 때문)
- 이벤트 핸들러 어트리뷰트 방식의 경우 이벤트 객체를 전달받으려면 이벤트 핸들러의 첫 번재 매개변수 이름이 반드시 event 여야함 ( 이벤트 핸들러 어트리뷰트 값은 사실 암묵적으로 생성되는 이벤트 핸들러 함수 몸체를 의미하기 때문
1. 이벤트 객체의 상속 구조
- Event, UIEvent, MouseEvent 등 모두 생성자 함수 (생성자 함수를 호출하여 이벤트 객체 생성 가능)
- 이벤트가 발생하면 암묵적으로 생성되는 이벤트 객체도 생성자 함수에 의해 생성
- 생성된 이벤트 객체는 생성자 함수와 더불어 생성되는 프로토타입으로 구성된 프로토타입 체인의 일원이 됨
- Event 인터페이스는 DOM 내에서 발생한 이벤트에 의해 생성되는 이벤트 객체를 나타냄
- Event 인터페이스에는 모든 이벤트 객체의 공통 프로퍼티가 정의되어 있고 하위 인터페이스(FocusEvent, MouseEvent 등 )에는 이벤트 타입에 따라 고유한 프로퍼티가 정의되어 있음
2. 이벤트 객체의 공통 프로퍼티
- Event 인터페이스의 이벤트 관련 프로퍼티는 모든 이벤트 객체가 상속받는 공통 프로퍼티
| 공통 프로퍼티 | 설명 | 타입 |
| type | 이벤트 타입 | string |
| target | 이벤트를 발생시킨 DOM 요소 | DOM 요소 노드 |
| currentTarget | 이벤트 핸들러가 바인딩된 DOM 요소 | DOM 요소 노드 |
| eventPhase | 이벤트 전파 단계 | number |
| bubbles | 이벤트를 버블링으로 전파하는지 여부. 다음 이벤트는 bubbles : false 버블링 X | boolean |
| cancelable | preventDefault 메서드를 호출하여 이벤트의 기본 동작을 취소할 수 있는지 여부. 다음 이벤트는 cancelable : false로 취소 X | boolean |
| defaultPrevented | preventDefault 메서드를 호출하여 이벤트를 취소했는지 여부 | boolean |
| isTrusted | 사용자 행위에 의해 발생한 이벤트인지 여부 | boolean |
| timeStamp | 이벤트가 발생한 시각 (1970/01/01/00:00:00 부터 경과한 밀리초) | number |
3. 마우스 정보 취득
- click, dbclick, mousedown, mouseup, mousemove, mouseenter, mouseleave 이벤트가 발생하면 생성되는 마우스 이벤트 타입의 이벤트 객체는 다음과 같은 고유의 프로퍼티를 가짐
- 마우스 포인터의 좌표 정보를 나타내는 프로퍼티 : screenX/screenY, clientX/clientY, pageX/pageY, offsetX/offsetY
- 버튼 정보를 나타내는 프로퍼티 : altKey, ctrlKey, shiftKey, button
4. 키보드 정보 취득
- keydown, keyup, keypress 이벤트가 발생하면 생성되는 KeyboardEvent 타입의 이벤트 객체는 altKey, ctrlKey, shiftKey, metaKey, key, keyCode 같은 고유의 프로퍼티를 가짐
이벤트 전파
- 이벤트 전파 : DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파됨
- 생성된 이벤트 객체는 이벤트를 발생시킨 DOM 요소인 이벤트 타겟을 중심으로 DOM 트리를 통해 전파됨
그림
- 캡쳐링 단계 : 이벤트가 상위 요소에서 하위 요소 방향으로 전파
- 타겟 단계 : 이벤트가 이벤트 타겟에 도달
- 버블링 단계 : 이벤트가 하위 요소에서 상위 요소 방향으로 전파
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
const $banana = document.getElementById("banana");
$fruits.addEventListener(
"click",
(e) => {
console.log(`이벤트 단계: ${e.eventPhase}`); // 이벤트 단계: 1 캡쳐링 단계
console.log(`이벤트 타겟: ${e.target}`); // 이벤트 타겟: [object HTMLLIElement]
console.log(`커렌트 타겟: ${e.currentTarget}`); // 커렌트 타겟: [object HTMLUListElement]
},
true
);
$banana.addEventListener("click", (e) => {
console.log(`이벤트 단계: ${e.eventPhase}`); // 이벤트 단계: 2 타겟 단계
console.log(`이벤트 타겟: ${e.target}`); // 이벤트 타겟: [object HTMLLIElement]
console.log(`커렌트 타겟: ${e.currentTarget}`); // 커렌트 타겟: [object HTMLUListElement]
});
$fruits.addEventListener("click", (e) => {
console.log(`이벤트 단계: ${e.eventPhase}`); // 이벤트 단계: 3 버블링 단계
console.log(`이벤트 타겟: ${e.target}`); // 이벤트 타겟: [object HTMLLIElement]
console.log(`커렌트 타겟: ${e.currentTarget}`); // 커렌트 타겟: [object HTMLUListElement]
});
</script>
</html>
- li 요소를 클릭하면 클릭 이벤트가 발생하여 클릭 이벤트 객체가 생성되고 클릭된 li 요소가 이벤트 타겟이 됨
- 클릭 이벤트 객체는 window에서 시작해서 이벤트 타겟 방향으로 전파됨 (캡쳐링 단계)
- 이후 이벤트 객체는 이벤트 타겟에서 시작하여 window 방향으로 전파됨 (버블링 단계)
- 이벤트는 이벤트를 발생시킨 이벤트 타겟은 물론 상위 DOM 요소에서도 캐치 가능 (이벤트 패스에 있는 모든 DOM 요소에서 캐치 가능)
- focus/ blur (포커스 이벤트), load/ unload/ abort/ error (리소스 이벤트), mouseenter/ mouseleave (마우스 이벤트) 이벤트 들은 버블링을 통해 전파되지 않음
이벤트 위임
- 이벤트 위임 : 여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하는 대신 하나의 상위 DOM 요소에 이벤트 핸들러를 등록하는 방법
- 이벤트 위임을 통해 상위 DOM 요소에 이벤트 핸들러를 등록하면 여러 개의 하위 DOM요소에 이벤트 핸들러를 등록할 필요가 없으며 동적으로 하위 DOM 요소를 추가하더라도 일일이 추가할 필요 X
- 일반적으로 객체의 target 프로퍼티와 currentTarget 프로퍼티는 동일한 DOM 요소를 가리키지만 상위 DOM 요소에 이벤트를 바인딩 한 경우 target 프로퍼티와 currentTarget 프로퍼티는 다른 DOM 요소를 가리킬 수 있음
<!DOCTYPE html>
<html>
<head>
<style>
#fruits .active {
color: red;
font-weight: bold;
}
</style>
</head>
<body>
<ul id="fruits">
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<div>선택된 목록 이름 : <em class="msg"></em></div>
</body>
<script>
const $fruits = document.getElementById("fruits");
const $msg = document.querySelector(".msg");
function activate({ target }) {
if (!target.matches("#fruits > li ")) return;
[...$fruits.children].forEach(($fruit) => {
console.log($fruit);
$fruit.classList.toggle("active", $fruit === target);
$msg.textContent = target.id;
});
}
$fruits.onclick = activate;
</script>
</html>
DOM 요소의 기존 동작 조작
1. DOM 요소의 기본 동작 중단
- 이벤트 객체의 preventDefault 메서드는 DOM 요소의 기본 동작을 중단시킴
2. 이벤트 전파 방지
- stopPropagation 메서드는 이벤트 전파를 중지시킴
이벤트 핸들러 내부의 this
1. 이벤트 핸들러 어트리뷰트 방식
- 이벤트 핸들러를 호출할 때 인수로 전달한 this 는 이벤트를 바인딩한 DOM 요소를 가리킨다
<!DOCTYPE html>
<html>
<body>
<button onclick="handleClick(this)">Click me</button>
</body>
<script>
function handleClick(button) {
console.log(button); // 이벤트를 바인딩한 button 요소
console.log(this); // window
}
</script>
</html>
- 이벤트 핸들러 어트리뷰트 방식에 의해 암묵적으로 생성된 이벤트 핸들러 내부의 this 는 이벤트를 바인딩한 DOM 요소를 가리킴
- 이벤트 핸들러 프로퍼티 방식과 동일
2. 이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식
- 이벤트 핸들러 프로퍼티 방식과 addEventListener 메서드 방식 모두 이벤트 핸들러 내부의 this 는 이벤트를 바인딩한 DOM 요소를 가리킴 (이벤트 핸들러 객체 === currentTarget 프로퍼티)
- 화살표 함수로 정의한 이벤트 핸들러 내부의 this 는 상위 스코프 this 를 가리킴 (화살표 함수는 자체 this 바인딩을 갖지 X
<!DOCTYPE html>
<html>
<body>
<button class="btn1">Click me</button>
<button class="btn2">Click me</button>
<button class="btn3">Click me</button>
</body>
<script>
const $button1 = document.querySelector(".btn1");
const $button2 = document.querySelector(".btn2");
const $button3 = document.querySelector(".btn3");
// 이벤트 핸들러 프로퍼티 방식
$button1.onclick = function (e) {
// this는 이벤트를 바인딩한 DOM 요소를 가리킴
console.log(this); // <button class="btn1">Click me</button>
console.log(e.currentTarget); // <button class="btn1">Click me</button>
};
// addEventListner 메서드 방식
$button2.addEventListener("click", function (e) {
// this는 이벤트를 바인딩한 DOM 요소를 가리킴
console.log(this); // <button class="btn1">Click me</button>
console.log(e.currentTarget); // <button class="btn1">Click me</button>
});
// 화살표 함수
$button3.onclick = (e) => {
console.log(this); // window
console.log(e.currentTarget); // <button class="btn3">Click me</button>
};
</script>
</html>
- 클래스에서 이벤트 핸들러를 바인딩 하는 경우 bind 메서드를 사용해서 this가 클래스가 생성할 인스턴스를 가리키도록 해야함
- 클래스에서 화살표 함수를 이용해 이벤트 핸들러로 등록하여 이벤트 핸들러 내부의 this 가 인스턴스를 가리키도록 가능 (프로토타입 메서드가 아닌 인스턴스 메서드)
<!DOCTYPE html>
<html>
<body>
<button class="btn1">0</button>
</body>
<script>
class App {
constructor() {
this.$button = document.querySelector(".btn1");
this.count = 0;
/*
// 1. increase 메서드를 이벤트 핸들러로 등록
this.$button.onclick = this.increase;
// 2 .increase 메서드 내부의 this 가 인스턴스를 가리키도록 한다
this.$button.onclick = this.increase.bind(this);
*/
// 화살표 함수인 increase 를 이벤트 핸들러로 등록
this.$button.onclick = this.increase;
}
/*
increase() {
1.
이벤트 핸들러 increase 내부의 this는 DOM 요소 (this.$button)을 가리킨다
따라서 this.$button 은 this.$button.$button
this.$button.textContent = ++this.count;// TypeError
2.
this.$button.textContent = ++this.count;
}
*/
// increase는 인스턴스 메서드이며 내부의 this 는 인스턴스를 가리킨다
increase = ()=> this.$button.textContent = ++ this.count;
}
new App();
</script>
</html>
이벤트 핸들러에 인수 전달
- 이벤트 핸들러 어트리뷰트 방식은 함수 호출문을 사용할 수 있기 때문에 인수를 전달할 수 있지만 이벤트 핸들러 프로퍼티 방식과 addEventListner 메서드 방식의 경우 이벤트 핸들러를 브라우저가 호출 X → 함수 호출, 이벤트 핸들러를 반환하는 함수 호출
<!DOCTYPE html>
<html>
<body>
<label>User name <input type="text" /></label>
<p class="msg"></p>
</body>
<script>
const $input = document.querySelector("input[type=text]");
const $msg = document.querySelector(".msg");
/*
const welcome = (name) => {
$msg.textContent = `${$input.value} 님 환영합니다 👋`;
};
// 이벤트 핸들러 내부에서 함수를 호출하면서 인수를 전달
$input.onblur = () => {
welcome(name);
};
*/
// 이벤트 핸들러를 반환하는 함수
const welcome = (name) => (e) => {
$msg.textContent = `${$input.value} 님 환영합니다 👋`;
};
// 이벤트 핸들러를 반환하는 함수를 호출하면서 인수 전달
$input.onblur = welcome(name);
</script>
</html>
커스텀 이벤트
1. 커스텀 이벤트 생성
- 커스텀 이벤트 : 개발자의 의도로 생성된 이벤트
- 이벤트 생성자 함수는 첫 번째 인수 : 이벤트 타입을 나타내는 문자열
- 이벤트 생성자 함수는 두 번째 인수 : bubbles, cancleable 프로퍼티 또는 이벤트 고유의 프로퍼티 값, 이벤트와 함께 전달하고 싶은 정보를 담은 detail 프로퍼티를 포함하는 객체
// MouseEvent 생성자 함수로 click 이벤트 타입의 커스텀 이벤트 객체 생성
const mouseEvent = new MouseEvent("click",{
bubbles : true,
cancelable : true,
clientX : 50;
clientY : 100
})
2. 커스텀 이벤트 디스패치
- 커스텀 이벤트는 dispatchEvent 메서드로 디스패치 (이벤트를 발생시키는 행위) 가능
- dispatchEvent 메서드에 이벤트 객체를 인수로 전달하면서 호출하면 인수로 전달한 이벤트 타입의 이벤트가 발생
- 이벤트 핸들러는 비동기 처리 방식으로 동작하지만 dispatchEvent 메서드는 동기 처리 방식으로 호출
- dispatchEvent 메서드를 호출하면 커스텀 이벤트에 바인딩된 이벤트 핸들러를 직접 호출하는 것 과 같음 → dispatch 메서드로 이벤트를 디스패치 하기 이전에 커스텀 이벤트를 처리할 이벤트 핸들러를 등록해야함
- 임의의 이벤트 타입을 지정하여 커스텀 이벤트 객체를 생성한 경우 반드시 addEventListener 메서드 방식으로 이벤트 핸들러를 등록해야 함
<!DOCTYPE html>
<html>
<body>
<button class="btn">Click me</button>
</body>
<script>
const $button = document.querySelector("button");
// 버튼 요소에 foo 커스텀 이벤트 핸들러 등록
// 커스텀 이벤트를 디스패치 하기 전에 이벤트 핸들러를 등록해야함
$button.addEventListener("foo", (e) => {
alert(e.detail.message);
});
// 커스텀 이벤트 생성자 함수로 foo 이벤트 타입의 커스텀 이벤트 객체 생성
const customEvent = new CustomEvent("foo", {
detail: { message: "Helloooooooooooooooo" }, // 이벤트와 함께 전달하고 싶은 정보
});
// 커스텀 이벤트 디스패치
$button.dispatchEvent(customEvent);
</script>
</html>
'Javascript' 카테고리의 다른 글
| [모던 자바스크립트 Deep Dive] 38. 비동기 프로그래밍 (0) | 2023.12.07 |
|---|---|
| [모던 자바스크립트 Deep Dive] 38. 타이머 (2) | 2023.12.06 |
| [모던 자바스크립트 Deep Dive] 36-3. DOM (1) | 2023.12.03 |
| [모던 자바스크립트 Deep Dive] 36-2. DOM (0) | 2023.12.02 |
| [모던 자바스크립트 Deep Dive] 36-1. DOM (1) | 2023.12.01 |