업데이트:

[01] 이벤트 드리븐 프로그래밍

클릭하고, 키보드를 입력하고, 마우스의 이동하는 것 모두 컴퓨터에서는 이벤트가 발생되었다고 한다.

이벤트가 발생했을 때 호출될 함수를 이벤트 핸들러라 하고,

이벤트가 발생했을 때 브라우저에게 이벤트 핸들러의 호출을 위임하는 것을 이벤트 핸들러 등록이라고 한다.

이벤트 중심으로 제어하는 프로그래밍 방식을 이벤트 드리븐 프로그래밍이라 한다.

[02] 이벤트 타입

이벤트 타입은 이벤트의 종류를 나타내는 문자열이다.

UI Event

Event Description
load 웹페이지의 로드가 완료되었을 때
unload 웹페이지가 언로드될 때(주로 새로운 페이지를 요청한 경우)
error 브라우저가 자바스크립트 오류를 만났거나 요청한 자원이 존재하지 않는 경우
resize 브라우저 창의 크기를 조절했을 때
scroll 사용자가 페이지를 위아래로 스크롤할 때
select 텍스트를 선택했을 때

Keyboard Event

Event Description
keydown 키를 누르고 있을 때
keyup 누르고 있던 키를 뗄 때
keypress 키를 누르고 뗏을 때

Mouse Event

Event Description
click 마우스 버튼을 클릭했을 때
dbclick 마우스 버튼을 더블 클릭했을 때
mousedown 마우스 버튼을 누르고 있을 때
mouseup 누르고 있던 마우스 버튼을 뗄 때
mousemove 마우스를 움직일 때 (터치스크린에서 동작하지 않는다)
mouseover 마우스를 요소 위로 움직였을 때 (터치스크린에서 동작하지 않는다)
mouseout 마우스를 요소 밖으로 움직였을 때 (터치스크린에서 동작하지 않는다)

Focus Event

Event Description
focus/focusin 요소가 포커스를 얻었을 때
blur/foucusout 요소가 포커스를 잃었을 때

Form Event

Event Description
input input 또는 textarea 요소의 값이 변경되었을 때
contenteditable contenteditable 어트리뷰트를 가진 요소의 값이 변경되었을 때
change select box, checkbox, radio button의 상태가 변경되었을 때
submit form을 submit할 때 (버튼 또는 키)
reset reset 버튼을 클릭할 때 (최근에는 사용 안함)

Clipboard Event

Event Description
cut 콘텐츠를 잘라내기할 때
copy 콘텐츠를 복사할 때
paste 콘텐츠를 붙여넣기할 때

[03] 이벤트 핸들러 등록

📌 이벤트 핸들러 : 이벤트가 발생했을 때 브라우저에 의해 호출될 함수

이벤트 핸들러를 등록하는 방법은 총 3가지이다.

1) 이벤트 핸들러 어트리뷰트 방식

html 요소의 어트리뷰트 중에는 onclick과 같은 이벤트 핸들러 어트리뷰트가 있다.

on 접두사와 이벤트 타입으로 어루어져 있다.

함수 호출문과 같은 “문”을 할당하면 이벤트 핸들러가 등록된다.

<!DOCTYPE html>
<html>
<body>
	<button onclick ="sayHi('Lee')">Click me!</button>
	<script>
			function sayHi(name){
				console.log(`Hi! ${name}.`};
			}
	</script>
</body>
</html>

React, View, Angular 의 경우 위 방식을 자주 쓰지만 자바스크립트에서는 더는 사용하지 않는것이 좋다고 한다.

✍ 아무래도 HTML 문서 안에서 함수를 자바스크립에 있는 함수를 호출하는 것이 인수를 넘기기에도 불편하고 성능상으로도 좋지 않아 보인다.

2) 이벤트 핸들러 프로퍼티 방식

<!DOCTYPE html>
<html>
<body>
<button>Click me!</button><!DOCTYPE html>
<html>
<body>
<button>Click me!</button>
	<script>
			const $button = document.querySelector('button');

			$button.onclick = function sayHi(name){
				console.log(`button click`};
			};
	</script>
</body>
</html>

프로퍼티 방식은 이벤트 타깃, 이벤트 타입, 이벤드 핸들러를 지정해주어야 한다.

Untitled

이벤트 핸들러 프로퍼티 방식은 이벤트 핸들러 어트리뷰트 방식에 비해 HTML과 자바스크립트가 뒤섞이는 문제를 해결할 수 있다.

그런데 단점은, 이벤트 핸들러 프로퍼티에 하나의 이벤트 핸들러만 바인딩할 수 있다는 것이다.

3) addEventListner 방식

위 모든 단점들을 해결할 수 있는 방법은 addEventListner 메소드를 이용하는 것이다.

Untitled

첫 번째 매개변수에는 이벤트의 종류를 나타내는 문자열인 이벤트 타입을 전달한다.

두 번째 매개변수에는 이벤트 핸들러를 전달한다.

세 번째 매개변수에는 이벤트 캡쳐링/ 버블링 사용여부를 전달한다.

이벤트 캡쳐링과 버블링은 40.6절 “이벤트 전파”에서 배울 것이다!

이제 코드를 통해 어떻게 사용되는지 살펴보자

<!DOCTYPE html>
<html>
<body>
	<button>Click me!</button>
	<script>
		const $button = document.querySelector('button');
		
		$button.addEventListener('click', function() {
			console.log('button click');
		});
	</script>
</body>
</html>

만약 동일한 요소에서 이벤트 핸들러 프로퍼티 방식과 addEventListner 방식을 모두 사용하면 둘 다 동작하게 된다.

그리고 addEventListner 는 2개의 이벤트 핸들러를 등록해도 모두 호출된다.

<!DOCTYPE html>
<html>
<body>
  <button>Click me!</button>
  <script>
    const $button = document.querySelector('button');

    $button.addEventListener('click', function() {
    console.log('[1] button click');
    })
    $button.addEventListener('click', function() {
    console.log('[2] button click');
});
  </script>
</body>
</html>

[04] 이벤트 핸들러 제거

EvenTarget.prototype.removeEvent를 사용하면 이벤트 핸들러가 제거된다.

<!DOCTYPE html>
<html>
<body>
	<button>Click me!</button>
	<script>
		const $button = document.querySelector('button');
		const handleClick = () => console.log('buton click')
		$button.addEventListener('click', handleClick);
		

		$button.removeEventListener('click', handleClick, true); // 제거 실패
		// addEventListener에 전달된 메서드와 일치해야 제거가 된다.
		$button.removeEventListener('click', handleClick); // 제거 성공
	</script>
</body>
</html>

주의할 것은 인수는 항상 addEventListener의 인수와 동일해야 한다는 것이다

그리고 addEventListener에서 익명 함수로 두번째 인자를 전달한 경우 addEventListener 밖에서는 제거할 수 없다. 익명 함수 안에서만 제거가 가능하다.

익명 함수 안에서 removeEventListener를 사용하면 이벤트 핸들러가 단 한 번만 호출된다.

이를 잘 활용하면 유용하게 쓰이는 상황도 있을 것 같다 !

[05] 이벤트 객체

이벤트 객체는 이벤트가 발생하면 동적으로 생성되어 이벤트 정보를 담고 있는 객체이다.

이벤트 핸들러의 첫 번째 인수로 전달된다.

<!DOCTYPE html>
<html>
<body>
  <p>클릭하세요. 클릭한 곳의 좌표가 표시됩니다.</p>
  <em class="message"></em>
  <script>
  function showCoords(e) { // e: event object
    const msg = document.querySelector('.message');
    msg.innerHTML =
      'clientX value: ' + e.clientX + '<br>' +
      'clientY value: ' + e.clientY;
		console.log(e); // PointerEvent {}
  }
  addEventListener('click', showCoords);
  </script>
</body>
</html>

Untitled

위 코드를 실행하고 이벤트 객체를 확인하면 PointerEvent라는 객체에 많은 이벤트 정보가 담겨있는 것을 확인할 수 있다.

이벤트 객체의 상속 구조

Untitled

위 예시에서는 PointerEvent가 출력되었지만 사실 이벤트 객체에는 다양한 이벤트들이 있다.

이벤트 객체의 상속구조에서 공통적으로 상속받는 Event 인터페이스에는 공통 프로퍼티가 정의되어 있고, FocusEvent, MouseEvent 등과 같은 하위 인터페이스에는 고유 프로퍼티가 정의되어 있다.

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

Untitled

이벤트의 객체의 프로퍼티 목록이다. 그 중 책에서는 노란색 형광펜 친 것들만 소개되어있다.

마우스 정보 취득

마우스 관련 이벤트가 발생하면 생성되는 이벤트 객체는 MouseEvent 객체이다.


🖱 MouseEvent 객체 고유 프로퍼티

  • 마우스 포인터 좌표 정보 : screenX/Y, clientX/Y, pageX/Y, offsetX/Y
  • 버튼 정보 : altKey, ctrlKey, shiftKey, button

키보드 정보 취득

키보드 관련 이벤트가 발생하면 생성되는 이벤트 객체는 KeyboardEvent 객체이다.


🎹 KeyboardEvent 객체 고유 프로퍼티

  • altKey, ctrlKey, metaKey, key, keyCode

[06] 이벤트 전파

<html>
<body>
	<ul id="fruits">
		<li id="apple">Apple</li>
		<li id="banana">banana</li>
		<li id="orange">orange</li>
	</ul>
</body>
</html>

위 코드에서 ul 요소의 자식 요소인 li 요소를 클릭하면 클릭 이벤트가 발생할 것이다.

이 때, 생성된 이벤트 객체는 이벤트를 발생시킨 DOM요소인 target을 중심으로 DOM트리를 통해 전파된다.

전파되는 방향에 따라 캡쳐링, 버블링으로 구분할 수 있다.

Untitled

  • 버블링 : 자식 요소에서 발생한 이벤트가 부모 요소로 전파되는 것
  • 캡처링 : 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것

💦 버블링

<!DOCTYPE html>
<html>
<head>
  <style>
    html, body { height: 100%; }
  </style>
<body>
  <p>버블링 이벤트 <button>버튼</button></p>
  <script>
    const body = document.querySelector('body');
    const para = document.querySelector('p');
    const button = document.querySelector('button');

    // 버블링
    body.addEventListener('click', function () {
      console.log('Handler for body.');
    });

    // 버블링
    para.addEventListener('click', function () {
      console.log('Handler for paragraph.');
    });

    // 버블링
    button.addEventListener('click', function () {
      console.log('Handler for button.');
    });
  </script>
</body>
</html>

버블링은 자식 요소에서 부모 요소로 전파된다고 했다. 아래 그림은 버튼을 클릭했을 때 출력되는 결과이다.

Untitled

html 문서를 보면 button 요소의 부모는 paragraph이고 paragraph의 부모는 body였다.

자식 요소인 button이 타깃으로 클릭되었지만 부모 요소인 paragraph와 body에도 전파되어 이벤트가 발생한 것을 알 수 있다.

만약 button이 아니라 버블링 이벤트라는 텍스트를 클릭하면 어떻게 될까?

paragraph의 부모 요소인 body까지만 이벤트가 전파될 것이다.

🖼 캡쳐링

<!DOCTYPE html>
<html>
<head>
  <style>
    html, body { height: 100%; }
  </style>
<body>
  <p>캡처링 이벤트 <button>버튼</button></p>
  <script>
    const body = document.querySelector('body');
    const para = document.querySelector('p');
    const button = document.querySelector('button');

    // 캡처링
    body.addEventListener('click', function () {
      console.log('Handler for body.');
    }, true);

    // 캡처링
    para.addEventListener('click', function () {
      console.log('Handler for paragraph.');
    }, true);

    // 캡처링
    button.addEventListener('click', function () {
      console.log('Handler for button.');
    }, true);
  </script>
</body>
</html>

Untitled

이번에도 버튼을 눌렀는데 버블링 이벤트와 반대의 순서로 콘솔 창에 출력된 것을 확인할 수 있다.

캡쳐링은 이벤트가 발생한 타깃의 부모요소에서 자식요소로 전파되는 것이기 때문이다.

[07] 이벤트 위임

만약 이벤트 위임이 없다면 아래 코드에서 각 item들에 클릭 이벤트를 넣어주고 싶을 때, 6개의 이벤트 핸들러를 바인딩해야 한다.

만약 만개의 리스트에 클릭 이벤트를 넣고 싶다면 엄청난 코드의 반복이 실행될 것이고 실행 속도가 매우 떨어질 것이다.

이를 해결할 수 있는 방법이 이벤트 위임이다.

<!DOCTYPE html>
<html>
<body>
  <ul class="post-list">
    <li id="post-1">Item 1</li>
    <li id="post-2">Item 2</li>
    <li id="post-3">Item 3</li>
    <li id="post-4">Item 4</li>
    <li id="post-5">Item 5</li>
    <li id="post-6">Item 6</li>
  </ul>
  <div class="msg">
  <script>
    const msg = document.querySelector('.msg');
    const list = document.querySelector('.post-list')

    list.addEventListener('click', function (e) {
      // 이벤트를 발생시킨 요소
      console.log('[target]: ' + e.target);
      // 이벤트를 발생시킨 요소의 nodeName
      console.log('[target.nodeName]: ' + e.target.nodeName);

      // li 요소 이외의 요소에서 발생한 이벤트는 대응하지 않는다.
      if (e.target && e.target.nodeName === 'LI') {
        msg.innerHTML = 'li#' + e.target.id + ' was clicked!';
      }
    });
  </script>
</body>
</html>

Untitled

위 코드는 부모 요소 ul 밑에 6개의 li 자식 요소가 있다.

이벤트 위임은 다수의 자식 요소에 각 이벤트 핸들러를 바인딩하는 대신 부모 요소에 이벤트 핸들러를 바인딩하는 방법이다.

이 방법이 가능한 이유는 이벤트 전파의 버블링 현상때문에 가능한 것이다.

[08] DOM 요소의 기본 동작 조작

DOM 요소의 기본 동작 중단

DOM 요소마다 기본 동작이 있다.

input요소에서 submit을 하면 새로고침 된다거나 a 요소를 클릭하면 지정된 링크로 이동하는 것처럼 말이다.

preventDefault 는 모든 기본동작을 중단시키는 메서드이다

이벤트 전파 방지

stopPropagation 메서드는 이벤트 전파를 중단시킨다.

[09] 이벤트 핸들러 내부의 this

책에서는 이벤트 핸들러 등록 방식 3가지에서 this를 소개하였지만 addEventListener 메서드 방식의 this만 가져왔다.

우선 this는 이벤트를 바인딩한 DOM요소를 가리킨다.

이벤트 객체의 currentTarget 프로퍼티와 같다고 생각하면 된다.

<!DOCTYPE html>
<html>
<body>
  <button class="btn1">0</button>
  <button class="btn2">0</button>
  <script>
    const $button1 = document.querySelector('.btn1');
    const $button2 = document.querySelector('.btn2');

    $button1.addEventListener('click', e => {
      console.log(this);
    })
    $button2.addEventListener('click', function(e) {
      console.log(this)
    })
  </script>
</body>
</html>

Untitled

두 개의 버튼은 화살표 함수로 호출했을 때와 함수로 호출했을 때의 방식을 비교한 것이다.

화살표 함수로 호출하면 this에 전역객체가 바인딩되고 함수 자체의 this를 갖지 않는다.

함수로 호출하면 이벤트를 바인딩한 DOM요소인 button을 가리키는 것을 확인할 수 있다.

클래스에서 이벤트 핸들러를 바인딩하는 경우의 this

함수와 다르게 클래스에서 this 활용은 주의할 필요가 있다.

아래 주석 코드를 실행하면 오류가 발생한다.

<!DOCTYPE html>
<html>
<body>
  <button class="btn">0</button>
  <script>
    class App ={
      constructor() {
        this.$button = document.querySelector('.btn');
        this.count = 0;
        //this.$button.onclick = this.increase;
				this.$button.onclick = this.increase.bind(this);
      }
      increase() {
        this.$button.textContent = ++this.count;
      }
    }
	new App();
  </script>
</body>
</html>

increase메서드 내부의 this는 클래스가 생성할 인스턴스가 아닌 이벤트를 바인딩한 DOM요소를 가리키기 때문에 this.$button을 가리킨다.

그러므로 bind를 사용하여 this를 전달해야 한다.

bind 바인딩 외에 increase메서드를 화살표 함수로 만드는 방법도 있다.

댓글남기기