JS에서 사용하는 이벤트를 알아보자 😙
안녕하세요! 두두코딩 입니다 ✋
오늘은 이벤트 개념에 대해 알아보겠습니다.
🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!
궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇
이벤트 드리븐 프로그래밍
브라우저는 처리해야할 특정 사건이 발생하면 “이벤트”를 발생 시킨다. 우리가 흔히 볼 수 있는 이벤트는 “클릭”, “키보드 입력”, “마우스 이등”등이 있는데, 이 동작이 브라우저 내에서 일어날 경우 감지하여 특정한 타입의 이벤트가 발생했다고 이벤트 핸들러에게 위임해 처리하도록 한다.
이벤트 핸들러란 이벤트가 발생했을 때 호출될 함수를 말한다.
아래의 코드를 통해 어떻게 이벤트가 발생하고 처리하는지 알아보도록 하자.
<!DOCTYPE html>
<html>
<body>
<button> Click me! </button>
<script>
const $button = document.querySelector('button');
$button.onclick() = () => { alert('button click'); };
</script>
</body>
</html>
위의 예시를 살펴보면 버튼 요소 $button의 onclick 프로퍼티에 함수를 할당했다. onclick
프로퍼티는 아래에서 자세히 다루도록 한다.
만약 button을 클릭할 경우, click 타입의 이벤트가 발생하게 되고, 해당 이벤트를 처리하는 핸들러 onclick
이 호출된다. 해당 함수 같은 경우 alert('button click')
을 호출하는 동작을 수행하고 있기 때문에 버튼을 클릭할 경우 alert
가 호출 된다.
위와 같이 이벤트와 그에 대응하는 함수(이벤트 핸들러)를 통해 사용자와 어플리케이션은 상호작용을 할 수 있다. 프로그램의 흐름을 이벤트 중심으로 제어하는 프로그래밍 방식을 우리는 이벤트 드리븐 프로그래밍이라고 한다.
이벤트 타입
이벤트 타입은 이벤트의 종류를 나타내는 문자열이다. 예를 들어 위 언급과 같이, 버튼(혹은 마우스)을 클릭할 경우 click
이벤트 타입이 발생하게 된다. Javascript에서 제공하는 이벤트 타입은 약 200여가지가 존재한다. 사용 빈도가 높은 몇 개의 이벤트 타입만 다루도록 하고, 자세한 내용은 여기를 클릭해 알아보자.
마우스 이벤트
- click => 마우스 버튼클릭
- dbclick => 마우스 더블 클릭
- mousedown => 마우스 버튼 누를 경우
- mouseup => 누르고 있던 마우스 버튼을 놓았을 경우
- mousemove => 마우스 커서가 움직일 경우
키보드 이벤트
- keydown => 모든 키보드 버튼을 누를 경우 발생
- keyup => 누르고 있던 키를 놓았을 때 한 번만 발생
폼 이벤트
- submit => form 요소 내의 submit 버튼을 클릭할 경우
- reset => form 내 reset 버튼을 클릭했을 경우
이벤트 핸들러 등록
이벤트 핸들러는 이벤트가 발생했을 때 브라우저에 호출을 위임한 함수다. 즉, 이벤트가 발생할 경우 브라우저가 인지해 해당 이벤트는 어떤 함수에서 처리해 할 때, 어떤 함수에 해당되는 것이 이벤트 핸들러이다.
우리는 브라우저에게 이벤트 핸들러 가지고 이벤트를 처리해달라고 위임하게 되며, 이를 이벤트 핸들러 등록 이라고 부른다. Javascript에서는 3가지 방법을 통해 이벤트 핸들러 등록한다.
이벤트 핸들러 어트리뷰트 방식
HTML 요소의 어트리뷰트 중에 이벤트에 대응하는 이벤트 핸들러 어트리뷰트가 존재한다. 이벤트 핸들러 어트리뷰트 같은 경우 on 접두사 + 이벤트 종류를 나타내는 이벤트 타입으로 이루어져있다. 예를들어 click
이라는 이벤트 타입 같은 경우 onclick
이라는 이벤트 핸들러 어트리뷰트가 존재한다.
이벤트 핸들러 어트리뷰트를 사용하기 위해서는 값으로 함수 호출문을 넣어주면 된다. 아래의 예시를 통해 알아보자.
<!DOCTYPE html>
<html>
<body>
<button onclick="sayHi('doo')">Click me! </button>
<script>
function sayHi(name) { console.log(`Hi! ${name}.`);
</script>
</body>
</html>
위와 같은 방법으로 사용하면 된다. 다만, 주의할 점은 이벤트 핸들러 어트리뷰트의 값으로 함수 참조가 아닌 함수 호출문을 넣어줘야한다는 것이다. sayHi
이라는 함수명만 적어주는 것이 아닌 sayHi('doo')
라는 명확한 함수 호출이 있어야한다는 것이다. 이벤트 핸들러 어트리뷰트 값 같은 경우 암묵적으로 생성될 이벤트 핸들러의 함수 몸체를 의미하기 때문이다.
// 암묵적으로 아래와 같이 생성됨
function onclick(event) {
// 함수명만 있으면 호출 안됨
// sayHi
sayHi('doo');
}
이벤트 핸들러 프로퍼티 방식
DOM 노드 객체는 이벤트에 대응하는 이벤트 핸들러 프로퍼티를 가지고 있다. 이벤트 핸들러 프로퍼티 키 같은 경우 어트리뷰트와 동일하게 on
이라는 접두어와 이벤트 타입
이 결합된 문자열을 의미한다. 이벤트 핸들러 프로퍼티에 함수를 바인딩하게 되면 이벤트 핸들러 등록이 된다.
<!DOCTYPE html>
<html>
<body>
<button>Click me! </button>
<script>
const $button = document.querySelector('button');
$button.onclick = function() {
console.log('button click');
};
</script>
</body>
</html>
위와 같이 프로퍼티에 function()
을 바인딩 하게 된다면 이벤트 핸들러가 등록된다.
이벤트 핸들러 어트리뷰트 와 이벤트 핸들러 프로퍼티 방식같은 경우 오직 하나의 이벤트만 바인딩이 가능하다는 단점이 있다.
<!DOCTYPE html>
<html>
<body>
<button>Click me! </button>
<script>
const $button = document.querySelector('button');
$button.onclick = function() {
console.log('button click 1');
};
$button.onclick = function() {
console.log('button click 2');
};
</script>
</body>
</html>
위와 같이 사용할 경우 오직 하나의 이벤트 핸들러만 등록되기 때문에 하나의 함수만 호출된다. 이를 보완하기 위해 addEventListner
메서드가 존재한다.
addEventListner 메서드 방식
위의 두 방식 (DOM Level 0)과 달리 addEventListener
방식은 DOM Level 2
에서 도입됐다.
위의 그림은 사용하는 방법을 나타낸다.
- EventTarget : 호출된 이벤트 타겟을 의미한다. (e.g. button)
- eventType : 접두사 “on” 을 붙이지 않고 타입을 적어준다 (e.g. click)
- fucntionName : 이벤트 핸들러
- useCapture : 이벤트 위임 관련한 정보
<!DOCTYPE html>
<html>
<body>
<button>Click me! </button>
<script>
const $button = document.querySelector('button');
$button.addEventListener('click', function() {
console.log('button click 1');
};
$button.addEventListener('click', function() {
console.log('button click 2');
};
</script>
</body>
</html>
위와 같이 이벤트 핸들러 등록을 할 경우 2개의 함수가 모두 호출되는 것을 볼 수 있을 것이다.
이벤트 핸들러 제거
이벤트 핸들러를 제거하는 방식은 다소 간단한데, removeEventListener
라는 메서드를 활용하면 된다. 이때 주의할 점이 있는데, addEventListener
메서드로 전달된 인자와 동일해야한다. 만약 다를 경우 제거되지 않는다는 점을 기억하자.
See the Pen graphic by 13634816 (@13634816) on CodePen.
이벤트 핸들러 프로퍼티로 등록된 경우 제거하기 위해서는 null
포인터로 함수 바인딩을 제거하면 된다.
이벤트 객체
이벤트가 발생하면 이벤트에 관련한 다양한 정보 (e.g. 마우스 위치 정보, check유무 등)가 이벤트 객체에 담겨 전달된다. 생성된 이벤트 객체는 이벤트 핸들러의 첫 번째 인수로 전달된다.
이벤트 객체의 상속구조에 대해 먼저 알아보자.
위와 같이 모든 객체는 Object
로 부터 파생된다. Event
같은 경우 최상위 객체로 Event
가 있고 해당 객체를 상속받아 구현되어져 있다. Event
객체내부에는 공통적으로 이벤트에서 필요한 프로퍼티 (e.g. 이벤트 타입, 현재 바인딩된 DOM 요소 등)들이 존재한다.
이벤트 핸들러 프로퍼티로 이벤트를 전달받을 경우를 예시를 통해 알아보자.
See the Pen graphic by 13634816 (@13634816) on CodePen.
위의 예시와 같이 첫번째 인자를 통해 이벤트를 전달받으며, 해당 이벤트의 정보를 활용할 수 있다는 것을 알 수 있다.
이벤트 핸들러 어트리뷰트 같은 경우 이벤트를 전달받기 위해 매개변수를 event
라는 이름으로 등록해야 이벤트를 전달받을 수 있다. 우선 아래의 예시를 보자.
See the Pen graphic by 13634816 (@13634816) on CodePen.
위와 같이 어트리뷰트로 전달할 경우 이벤트 핸들러의 첫 번째 매개변수가 꼭 event
여야 제대로 동작한다. 그 이유는 무엇일까?
위에서도 살짝 언급을 했는데 이벤트 핸들러 어트리뷰트 같은 경우 컴파일시 암묵적으로 아래와 같은 형식으로 변환된다. 즉, 암묵적으로 event
라는 이름으로 이벤트가 전달되기 때문에 내가 사용하는 함수에게 event를 전달해주고 싶다면 event
라는 이름을 통해 전달해야한다.
// 암묵적으로 onclick=showCoords(event) 는 변환됨.
fucntion onclick(event) {
showCoords(event);
}
이벤트 전파
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>
</html>
위의 예시에서 2번째 li
요소를 클릭했다고 가정해보자. 그럴 경우 li
요소에서 click
이벤트가 발생하게 된다. 그렇다면 li
는 어떻게 자신이 event가 발생했는지 알 수 있을까?
li
요소에서 이벤트가 발생했다고 알 수 있는 이유는 이벤트 전파 때문이다. 아래의 그림을 통해 이벤트 전파 개념에 대해 알아보자. 그림과 같이 Window 내에서 클릭 이벤트가 발생하게 될 경우 DOM tree를 탐색하게 된다. 이후 Event Target을 만나게 될 경우 li
요소에 이벤트 핸들러를 발생시킨다. 이후 상위 window까지 돌아온다. (일종의 부메랑을 생각하면 쉽다)
- 캡처링 단계: 이벤트가 상위 요소에서 하위 요소로 전파
- 타깃 단계: 이벤트가 이벤트 타깃에 도달
- 버블링 단계 : 이벤트가 하위 요소에서 상위 요소 방향으로 전파
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li id="apple"> Apple </li>
<li id="banana"> Banana </li>
<li id="orange"> Orange </li>
</ul>
<script>
const $fruit = document.getElementById('fruits');
const $banana = document.getElementById('banana');
// 1. 캡처링 2. 타깃 단계, 3. 버블링 단계
// 3번째 인자를 true로 주게 되면 캡처링 단계 캐치 가능 (어트리뷰트와 프로퍼티는 불가능)
$fruit.addEventListener('click', e => {
console.log(`이벤트 단계: ${e.eventPhase}`); // 1. 캡처링 단계
console.log(`이벤트 타깃: ${e.target}`); // [object HTMLLIEelemnt]
console.log(`이벤트 타깃: ${e.currentTarget}`); // [object HTMLULElement]
}, true);
$fruit.addEventListener('click', e => {
console.log(`이벤트 단계: ${e.eventPhase}`); // 2. 타겟 단계
console.log(`이벤트 타깃: ${e.target}`); // [object HTMLLIEelemnt]
console.log(`이벤트 타깃: ${e.currentTarget}`); // [object HTMLLIElement]
});
$fruit.addEventListener('click', e => {
console.log(`이벤트 단계: ${e.eventPhase}`); // 3. 버블링 단계
console.log(`이벤트 타깃: ${e.target}`); // [object HTMLLIEelemnt]
console.log(`이벤트 타깃: ${e.currentTarget}`); // [object HTMLULElement]
});
</script>
</body>
</html>
버블링이 있어 이벤트는 이벤트를 발생시킨 이벤트 타깃은 물론 상위 DOM 요소에서도 캐치가 가능하다.
DOM 요소의 기본동작 조작
DOM 요소의 기본 동작 중단
DOM 요소는 저마다 기본 동작이 있다. 우리가 흔히아는 <a>
같은 경우 href
어트리뷰트로 지정된 링크로 이동하는 동작은 기본으로 정의되어져 있다. 위와 같이 기본으로 정의된 이벤트 핸들러 동작을 preventDefault
라는 메서드를 활용해 중단할 수 있다.
See the Pen graphic by 13634816 (@13634816) on CodePen.
이벤트 전파 방지
이벤트 전파로 인해 타겟단계에서 버블링 단계로 진행돼 상위 DOM 노드에 접근이 가능했다. 우리가 상위 DOM 노드에 접근하지 않으면 타겟 단계에서 이벤트 전파 방지 동작을 수행해야한다. 아래의 예시를 통해 알아보자.
See the Pen graphic by 13634816 (@13634816) on CodePen.
해당 방법 같은 경우 오직 자신의 이벤트만 받고싶을때 사용한다.
이벤트 핸들러에 인수 전달
함수에 인수를 전달하기 위해서는 함수 호출 시 전달해야한다. 이벤트 핸들러 프로퍼티 방식과 addEventHandler 같은 경우 브라우저 자체적으로 함수를 호출해주기 때문에 인수를 전달할 수 없을 것 같다… 아예 방법이 없는 것이 아니라 아래와 같은 방법을 사용할 경우 인수를 전달할 수 있다.
(보통 학번 6자리 입력하세요 같은 케이스가 blur 표시로 적혀있는 경우가 많이 있었다..)
See the Pen graphic by 13634816 (@13634816) on CodePen.