Leon Chaewon Kong's dev blog

JavaScript의 이벤트(event)

이벤트 개념, 이벤트 등록, 이벤트 버블링, 이벤트 캡처링, 이벤트 위임, 이벤트 종류 등 JavaScript와 HTML의 이벤트에 대해 알아본다.

이벤트란 “어떤 사건”을 의미한다.

JavaScript와 HTML의 상호작용은 이벤트 핸들링에 의해 이루어진다. 사용자가 이벤트를 발생시키면 그 이벤트를 JavaScript 코드로 가로채서 기대하는 행위가 브라우저에 의해 이루어지도록 코딩하는 것이다.

이벤트 프로그래밍: JS를 활용해 어떤 일이 발생될 경우 실행되어야 하는 코드를 등록하고, 브라우저는 그 일이 발생했을 때 등록된 코드를 실행하는 것이다.

<input type="button" onclick="alert(window.location)" value="alert(window.href)" />
  • event target: 이벤트가 일어날 객체

    이 코드에서 타겟은 버튼 태그에 대한 객체가 된다.

  • event type: 이벤트의 종류. 위 코드에서는 click 이 이벤트 타입에 해당.

  • event handler: 이벤트가 발생했을 때 동작하는 코드. 일반적으로 콜백함수가 활용됨. 위 코드에서는 alert(window.location)

이벤트 등록하기

이벤트 핸들러를 등록해주는 방법을 알아보자.

이벤트 등록 방법에는 1) 인라인 방식, 2) 프로퍼티 리스너 방식, 3) addEventListener 를 이용하는 방식이 있다.

1. 인라인 방식

 <input type="button" onclick="alert(window.location)" value="alert(window.href)" />

위에서 살펴본 예와 같다. 이벤트를 이벤트 대상의 태그 속성으로 지정하는 것이다.

2. 프로퍼티 리스너 방식

이벤트 대상에 해당하는 객체의 프로퍼티로 이벤트를 등록하는 방식이다.

인라인 방식에 비해 HTML과 JavaScript를 분리해 관리할 수 있다는 장점을 가진다.

<input type="button" id="target" value="button" />

<script>
	let target = document.getElementById('target');
  target.onclick = () => alert("Hi there")
</script>

3. addEventListener 방식

권장되는 이벤트 등록 방법이다.

HTML과 JavaScript를 분리해 작성하여 모듈성을 확보하면서도, 복수의 이벤트 핸들러를 손쉽게 등록할 수 있다.

<input type="button" id="target" value="button" />

<script>
	let target = document.getElementById('target');
  target.addEventListener('click', () => alert("Hi there"))
</script>

이벤트 버블링(Event Bubbling)

이벤트 버블링은 특정 요소에서 이벤트가 발생했을 때 그 이벤트가 더 상위의 요소들로 전달되어 가는 특성을 의미한다.

HTML 요소가 가지는 트리 구조 상의 상위 요소로 전파되어 간다는 것이다.

<body>
	<div class="one">
		<div class="two">
			<div class="three">
        안녕하세요
			</div>
		</div>
	</div>
</body>
let divs = document.querySelectorAll('div')
divs.forEach(div => {
    div.addEventListener('click', logEvent);
});

const logEvent = event => console.log(event.currentTarget.className)

위 코드를 실행하면 “third” -> “second” -> “first” 순으로 콘솔 로깅이 찍히게 된다.

브라우저는 특정 요소에서 이벤트가 발생할 경우 그 이벤트를 최상위 요소까지 전파시킨다. 이러한 전파 방식을 이벤트 버블링 이라고 한다.

이벤트 캡처링(Event Capturing)

이벤트 버블링이 하위요소에서부터 상위요소로 트리를 거슬러 올라가며 이벤트를 전달시키는 것이었다면. 이벤트 캡처링은 상위요소에서부터 이벤트를 하위요소로 점차 탐색해 가는 전파 방식이다.

그렇다면 이벤트 캡처링은 어떻게 구현할 수 있을까?

capture: trueaddEventListener 의 옵션 객체로 설정해주면 된다.

let divs = document.querySelectorAll('div')
divs.forEach(div => {
    div.addEventListener('click', logEvent, {
      capture: true
    });
});

const logEvent = event => console.log(event.currentTarget.className)

default는 false 이기 때문에, 생략할 경우 이벤트 버블링 방식으로 작동한다.

capture: true 로 설정할 경우, “first” -> “second” -> “third” 순으로 콘솔에 로그가 찍히게 된다.

한편, event.stopPropagation() 을 사용해 이벤트의 전파를 막을 수 있다.

  • 이벤트 버블링의 경우: 클릭한 요소의 이벤트만 발생
  • 이벤트 캡처링의 경우: 클릭한 요소의 최상위 요소의 이벤트만 발생

이벤트 위임(Event Delegation)

이벤트 위임은 하위 요소에 각각 이벤트를 붙이지 않고, 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식을 의미한다.

<ul class="itemlist">
    <li>
        <input type="button" value="button1">
    </li>
    <li>
        <input type="button" value="button2">
    </li>
</ul>

다음과 같은 코드가 있다고 하자.

각 버튼 별로 클릭할 경우 “버튼명”이 클릭되었음을 alert 하고 싶다.

기존에 살펴 본 addEventListener 방식을 활용하면 다음과 같을 것이다.

let buttons = document.querySelectorAll('input')
buttons.forEach(button => button.addEventListener('click', event => alert(`${event.target.value} clicked!`)))

만약, 버튼이 동적으로 계속 추가되는 구조라면 어떻게 될까?

let buttons = document.querySelectorAll('input')
buttons.forEach(button => button.addEventListener('click', event => alert(`${event.target.value} clicked!`)))


// button 3 추가
let itemList = document.querySelector('.itemlist');
let li = document.createElement('li');
let input = document.createElement('input');

input.setAttribute('type', 'button');
input.setAttribute('value', 'button3');
li.appendChild(input);
itemList.appendChild(li);

기존의 JavaScript 코드 밑에 button3 가 추가되는 코드를 추가했다. 이제 버튼은 모두 3개이다.

button1button2는 클릭할 경우 정상적으로 alert가 작동하지만 button3 는 이벤트 핸들러를 등록하지 않았기 때문에 클릭하더라도 아무런 일도 발생하지 않는다.

다른 버튼처럼 클릭 이벤트가 활성화되기 위해서는 아래처럼 이벤트를 추가할 수도 있다.

let buttons = document.querySelectorAll('input')
buttons.forEach(button => button.addEventListener('click', event => alert(`${event.target.value} clicked!`)))

let itemList = document.querySelector('.itemlist');

let li = document.createElement('li');
let input = document.createElement('input');

input.setAttribute('type', 'button');
input.setAttribute('value', 'button3');

// 이벤트리스너 추가
input.addEventListener('click', event => alert(`${event.target.value} clicked!`))
li.appendChild(input);
itemList.appendChild(li);

하지만, 매번 요소가 새로 추가될 때마다 이벤트리스너를 새로 물리는 것은 번잡하다.

위 코드는 아래 코드처럼 리팩토링 할 수 있다.

let itemlist = document.querySelector('.itemlist')
itemlist.addEventListener('click', event => alert(event.target.value + " clicked!"))

// 새 버튼 추가
let li = document.createElement('li');
let input = document.createElement('input');

input.setAttribute('type', 'button');
input.setAttribute('value', 'button3');
li.appendChild(input);
itemlist.appendChild(li);

이벤트 버블링의 원리를 활용해 하위에서 발생한 클릭 이벤트를 상위 요소인 ul 태그에서 감지하는 것이다.

이렇듯 이벤트 위임(event delegation)을 이용하면 편리하게 이벤트를 관리하고 추가할 수 있다.

주요 이벤트 종류와 의미

event 종류 의미
Blur 포커스를 다른 곳으로 옮길 경우
click 링크나 폼의 구성원을 클릭할 때
change 선택값을 변경하는 경우
focus 포커스가 위치할 경우
mouseover 마우스 커서를 대상에 올리는 경우
mouseout 마우스 커서가 대상에서 떠날 경우
mousedown 마우스를 누를 경우
mousemove 마우스를 움직일 경우
mouseup 마우스를 눌렀다 놓을 경우
select 입력양식에서 하나가 선택될 경우
submit 폼의 데이터를 전송하는 경우
load 페이지나 윈도우가 브라우저로 읽어지는 경우
unload 현재의 브라우저나 윈도우를 떠날 경우
error 문서, 이미지 등에서 에러를 만날 경우
reset 리셋 버튼이 눌렸을 경우
dbclick 더블클릭을 하는 경우
dragdrop 마우스를 누르고 움직일 경우
keydown 키 입력시
keypress 키를 누를 경우
keypu 키를 누르고 놓았을 경우
move 윈도우나 프레임을 움직일 경우
resize 윈도우나 프레임 사이즈를 조절할 경우

참고목록 및 출처