브라우저에서 이벤트란 웹 브라우저가 알려주는 HTML 요소에 대한 사건의 발생을 의미한다. 사용자(혹은 브라우저)가 버튼을 클릭하거나 input에 값을 입력하는 등의 HTML 페이지를 조작할 때 발생하는 것을 이벤트라고 한다.
웹 페이지에 사용된 자바스크립트는 이렇게 발생한 이벤트에 반응하여 특정 동작을 수행할 수 있으며 이를 통해 사용자와 웹페이지 간 인터랙션이 가능하게 된다.

이벤트 타입

브라우저 이벤트 종류 중 사용 빈도가 높은 이벤트 타입

이벤트 종류이벤트 타입이벤트 발생 시점
마우스 이벤트click마우스 클릭했을 때
dbclick마우스 더블 클릭했을 때
mousedown마우스 버튼 눌렀을 때
mouseup누르고 있던 마우스 버튼 놓았을때
mousemove마우스 커서 움직였을때
mouseenter마우스 커서를 HTML 요소 안으로 이동했을때(버블링 x)
mouseover마우스 커서를 HTML 요소 안으로 이동했을때(버블링 o)
mouseleave마우스 커서를 HTML 요소 밖으로 이동했을때 (버블링 x)
mouseout마우스 커서를 HTML 요소 밖으로 이동했을때 (버블링 o)
키보드 이벤트keydown키보드 키 눌렀을때 발생. 단, 문자, 숫자, 특수 문자, enter 키 눌렀을때는 연속적으로 발생하고 그 외 키는 한번만 발생
keyup누르고 있던 키 놓았을때 한 번만 발생
포커스 이벤트focusHTML 요소가 포커스 받았을때(버블링 x)
blurHTML 요소가 포커스를 잃었을때 (버블링 x)
focusinHTML 요소가 포커스 받았을때(버블링 o)
focusoutHTML 요소가 포커스를 잃었을때 (버블링 o)
폼 이벤트submitform 요소 내 submit 버튼 클릭 했을때
resetform 요소 내 reset 버튼 클릭 했을때
값 변경 이벤트inputinput(text, checkbox, radio), select, textarea 요소의 값이 입력되었을때
changeinput(text, checkbox, radio), select, textarea 요소의 값이 변경되었을때(사용자가 입력을 종료하고 요소가 포커스를 잃었을때)
readystatechangeHTML 문서의 로드와 파싱 상태를 나타내는 document.readyState 프로퍼티 값(’loading’, ‘interactive’, ‘complete’)이 변경될 때
DOM 뮤테이션 이벤트DOMContentLoadedHTML 문서의 로드와 파싱이 완료되어 DOM 생성이 완료되었을때
뷰 이벤트resize브라우저 윈도우의 크기를 리사이즈할때 연속적으로 발생(오직 window 객체에서만 발생)
scroll웹페이지(document) 또는 HTML 요소를 스크롤할때 연속적으로 발생
리소스 이벤트loadDOMContentLoaded 이벤트가 발생한 이후, 모든 리소스(이미지, 폰트 등)의 로딩이 완료되었을때(주로 window 객체에서 발생)
unload리소스가 언로드될때(주로 새로운 웹페이지 요청한 경우)
abort리소스 로딩이 중단되었을때
error리소스 로딩이 실패했을때

이벤트 핸들러 등록

이벤트 핸들러란?
이벤트 발생시 호출되는 함수

이벤트 핸들러 등록이란?
이벤트 발생 시 브라우저에게 이벤트 핸들러 호출을 위임하는 것

이벤트가 언제 발생할 지 모르기 때문에 프로그래머가 함수를 호출하는 것이 아니고 브라우저가 이벤트를 감지해서 이벤트 핸들러를 호출할 수 있도록 프로그래머가 이벤트 핸들링을 브라우저에게 위임하는 것을 의미

등록 방법 1. 이벤트 핸들러 어트리뷰트 방식

  • HTML 속성(어트리뷰트)값으로 이벤트 핸들러를 등록하는 방법
  • 어트리뷰트 이름 : on + 이벤트 타입 (ex. onclick, onchange)
<body>
	<button onclick="greeting()">Click</button>
	<script>
		function greeting(){
			alert('Hello World')
		}
	</script>
</body>

어트리뷰트 onclick="greeting()"를 파싱하면 자바스크립트 엔진이 아래와 같은 함수를 암묵적으로 생성한다.

function onclick(event){
	greeting()
}

등록 방법 2. 이벤트 핸들러 프로퍼티 방식

window 객체, Document, HTMLElement 타입의 DOM 노드 객체는 각각 이벤트에 대응하는 이벤트 핸들러 프로퍼티(ex. window.onload, document.onreadystatechange, input.onchange 등)를 갖고 있다.

// 이벤트 타겟
const $button = document.querySelector('button');

function greeting() {
	alert('Hello World')
}

// 이벤트 핸들러 프로퍼티에 이밴트 핸들러 바인딩
$button.onclick = function () {
	greeting()
}
  • 단점 : 하나의 이벤트에 하나의 이벤트 핸들러 함수만 바인딩할 수 있다.

등록 방법 3. addEventListener 메서드 방식

메서드의 세번째 인자로 capture 사용 여부를 boolean 값으로 줄 수 있다. true를 줄 경우 후술할 캡쳐링 단계의 이벤트를 캐치한다.

$button.addEventListener('click', function(){
	greeting()
})
  • 동일한 HTML 요소에 여러 개의 이벤트 핸들러를 등록 할 수 있다.
<body>
  <button>Click</button>
  <p class="first"></p>
  <p class="second"></p>
 </body
const $button = document.querySelector('button');
const $text1 = document.querySelector(".first");
const $text2 = document.querySelector(".second");


// $button 요소에 첫번째 이벤트 핸들러 등록
$button.addEventListener("click", function () {
  $text1.innerHTML = "첫번째 이벤트 핸들러 호출";
});
// $button 요소에 두번째 이벤트 핸들러 등록
$button.addEventListener("click", function () {
  $text2.innerHTML = "두번째 이벤트 핸들러 호출";
});

//버튼을 클릭하면 두 이벤트 핸들러가 순차적으로 실행된다.


  • 동일한 HTML 요소의 동일 이벤트 타입에 이벤트 프로퍼티 방식과 addEventListener 메서드방식으로 이벤트 핸들러를 등록하면 2개의 이벤트핸들러 모두 호출된다.
const $button = document.querySelector('button');

// $button 요소의 click 이벤트에 이벤트 핸들러 바인딩
// 이벤트 프로퍼티 방식
$button.onclick = function () {
	console.log('onclick 프로퍼티 button click')
}

// addEventListener 방식
$button.addEventListener('click', function(){
	console.log('addEventListener 메서드 button click')
})
// 버튼을 클릭하면 두 개의 이벤트 핸들러가 차례로 호출된다.

이벤트 핸들러 제거

removeEventListener 메서드

function sayHi(){
	console.log('hi')
}
function handleClick(){
	console.log('click')
}

$button.addEventListener('click', sayHi)
$button.addEventListener('click', handleClick)
$button.removeEventListener('click', handleClick) // 이벤트 핸들러 제거됨

위 코드를 실행하여 버튼을 클릭하면 첫번째 이벤트 핸들러인 sayHi 함수만 호출되어 "hi"만 콘솔에 출력된다. 두번째 이벤트 핸들러인 handleClick 함수는 removeEventListener 메서드로 제거되었으므로 클릭 이벤트가 발생해도 호출되지 않는다.


  • 이벤트 핸들러를 제거하려면 이벤트 핸들러의 참조를 변수나 자료구조에 저장하고 있어야 한다.
  • 등록되어있는 이벤트 핸들러를 참조하여 제거하므로 이벤트 핸들러가 무명 함수인 경우 제거할 수 없다.
// 무명 함수를 이벤트 핸들러로 등록한 경우
$button.addEventListener('click', () => console.log('hi'))
//removeEventListener가 해당 무명함수를 참조할 수 없으므로 등록된 이벤트 핸들러를 제거할 수 없다.
// $button.removeEventListener('click', () => console.log('hi'))

제거하지 못하는 이유
'hi'를 콘솔에 출력한다는 함수의 기능은 동일하지만 addEventListener 메소드로 등록한 이벤트 핸들러 () => console.log('hi')removeEventListner 메소드로 제거하려는 이벤트 핸들러 () => console.log('hi')는 다른 함수이기 때문이다.

이벤트 핸들러 프로퍼티 방식의 경우

프로퍼티에 null을 할당한다.

function handleClick(){
	console.log('click')
}

$button.onclick = handleClick;

$button.removeEventListener('click', handleClick) // 제거되지 않는다. 
$button.onclick = null; // 이벤트 핸들러 제거됨

이벤트 객체

  • 이벤트 발생 시 동적으로 생성되는 객체. 이벤트 관련 다양한 정보 담고 있다.
  • 이벤트 핸들러의 첫번째 인수로 전달된다. 따라서 이벤트 객체를 전달받기 위해서는 이벤트 핸들러 정의 시 매개변수를 명시적으로 선언해야한다.
  • 이벤트 핸들러 어트리뷰트 방식으로 이벤트 객체 전달 시 매개변수명은 event를 사용해야한다.
<!-- event가 아닌 다른 매개변수명을 사용하면 에러 발생 -->
<button onclick="console.log(v)">매개변수 v</button>
<button onclick="console.log(event)">매개변수 event</button>

이벤트 객체의 공통 프로퍼티

공통 프로퍼티설명타입추가 설명
type이벤트 타입string
target이벤트를 발생시킨 DOM 요소DOM 요소 노드
currentTarget이벤트 핸들러가 바인딩된 DOM 요소DOM 요소 노드
eventPhase이벤트 전파 단계number0 : 이벤트 없음
1 : 캡처링 단계
2 : 타깃 단계
3 : 버블링 단계
bubbles이벤트를 버블링으로 전파하는지 여부booleanbubbles: false(버블링 안함)인 이벤트들
- 포커스 이벤트(focus/blur)
- 마우스 이벤트(mouseenter/mouseleave)
- 리소스 이벤트(load/unload/abort/error)
cancelablepreventDefault 메서드로 이벤트 기본 동작 취소가능한지 여부booleancancelable: false로 취소할 수 없는 이벤트들
- 포커스 이벤트(focus/blur)
- 마우스 이벤트(mouseenter/mouseleave)
- 리소스 이벤트(load/unload/abort/error)
defaultPreventedpreventDefault 메서드로 이벤트 취소했는지 여부boolean
isTrusted사용자 행위에 의해 발생한 이벤트인지 여부boolean
timeStamp이벤트가 발생한 시각number

이벤트 전파

👉 DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트가 DOM 트리를 통해 전파되는 것

이벤트 캡쳐링

DOM의 최상단에서부터 이벤트 타겟 노드로, DOM 트리의 위에서 아래로 전파

<body>
	<div id="container">
		<p>
			<button>Click</button>
		</p>
	</div>
</body>

const $div = document.getElementById("container");
const $para = document.querySelector("p");
const $button = document.querySelector("button");

const handleClick = (e) => {
  console.log(
    `이벤트 타겟: ${e.target.nodeName} / currentTarget: ${e.currentTarget.nodeName} / 이벤트 전파 단계: ${e.eventPhase}`
  );
};

// addEventListener 메서드의 세번째 인자에 true를 넘겨주면 캡쳐링을 감지한다.
$div.addEventListener("click", handleClick, true);
$para.addEventListener("click", handleClick, true);
$button.addEventListener("click", handleClick, true);

이벤트 버블링

이벤트 타겟 노드에서 상위 요소로, DOM 트리의 하위에서 상위로 전파

// caturing: true값을 false로 넣어주거나 생략하면 버블링을 감지한다.
$div.addEventListener("click", handleClick);
$para.addEventListener("click", handleClick);
$button.addEventListener("click", handleClick);

이벤트 전파 방지

stopPropagation 메서드를 사용해서 이벤트 전파를 중단시킬 수 있다.

// 이벤트 전파 방지하는 이벤트 핸들러 생성
const stopHandler = (e) => {
	console.log(e.target.nodeName);
	e.stopPropagation();
}

$div.addEventListener('click', handleClick);
$para.addEventListener('click', stopHandler); // $para 요소의 상위 요소인 $div 요소로의 이벤트 전파를 중단한다.
$button.addEventListener('click', handleClick);

이벤트 타겟인 $button부터 이벤트 버블링이 시작되어 다음 상위 요소인 $para 요소에 click 이벤트가 전파 되었을때 stopPropagation 메서드가 실행되어 그 다음 상위 요소로의 이벤트 전파를 중단한다. 따라서 click 이벤트가 상위 요소인 $div 요소에 전달되지 않기 때문에 해당 요소의 이벤트 핸들러인 handleClick은 호출되지 않는다.

이벤트 위임

이벤트 전파의 특성을 활용하여, 여러 개의 하위 DOM 요소에 일일이 이벤트 핸들러를 등록하는 것이 아닌 상위 요소에 이벤트 핸들러를 등록, 즉 상위 요소에 이벤트를 위임하여 하위 요소들의 이벤트를 처리할 수 있다.

이벤트 위임의 필요성

  • 동적으로 요소 추가하는 경우 - 추가할 때마다 이벤트 리스너 달아줘야 하는 번거로움
  • 이벤트를 감지해야하는 요소가 수십,수백개인 경우
    • 일일이 이벤트 핸들러를 등록해야하는 번거로움
    • 이벤트 핸들러 등록 개수 많을 수록 성능 저하됨

이벤트 위임 예시

li 요소를 클릭하면 해당 요소에 class를 추가하여 font-weight를 bold로 변경해주는 예시를 들어보자.

<head>
	<style>
		.active {
			font-weight: bold;
		}
	</style>
</head>
<body>
	<ul id="colors">
		<li class="red">Red</li>
		<li class="yellow">Yellow</li>			
	</ul>
</body>

이벤트 위임 안한 경우

const $colors = document.getElementById('colors')

// $colors 요소의 자식 요소들인 li 요소들을 클릭했을 때, active라는 class를 추가해주는 함수
function activate({ target }){
	[...$colors.children].forEach($color => {
		$color.classList.toggle('active', $color === target )
}

// $colors 요소의 자식 요소에 각각 이벤트 핸들러 등록
document.querySelector('.red').addEventListener('click', activate)
document.querySelector('.yellow').addEventListener('click', activate)

// li 요소 추가
const li = document.createElement("li");
const text = document.createTextNode("Green");
li.appendChild(text);
li.setAttribute("class", "green");
$colors.appendChild(li);

마지막에 동적으로 li 요소(Green)를 추가했는데 이 요소를 클릭해도 아무 변화가 없다. 이벤트 리스너를 달아주지 않았기 때문이다.

이벤트 위임한 경우

const $colors = document.getElementById('colors')

function activate({ target }){
	[...$colors.children].forEach($color => {
		$color.classList.toggle('active', $color === target)
}

// li 요소 추가
const li = document.createElement("li");
const text = document.createTextNode("Green");
li.appendChild(text);
li.setAttribute("class", "green");
$colors.appendChild(li);

// 자식 요소가 아닌 상위 요소 $colors에 이벤트 핸들러 등록
$colors.addEventListener("click", activate);

$colors 요소의 자식 요소인 li 요소가 아닌, $colors 요소에 이벤트 핸들러를 등록했다. 이러한 이벤트 위임에 따라 동적으로 추가한 li요소(Green)를 클릭하면 이벤트 핸들러인 activate 함수가 호출되어 font-weight가 bold로 변한다.
이처럼 이벤트 위임을 활용하면 동적으로 추가한 자식 요소에 이벤트 핸들러를 등록하지 않아도 상위 요소가 자식 요소의 이벤트를 감지해서 이벤트 핸들러가 호출된다.


참고자료
https://developer.mozilla.org/en-US/docs/Web/Events
https://poiemaweb.com/js-event
https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/
https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle#setting_the_force_parameter
http://www.tcpschool.com/javascript/js_event_concept

profile
얼레벌레 프론트엔드 개발자

0개의 댓글