이벤트 (Event)

김동현·2021년 9월 5일
0

JavaScript

목록 보기
24/32
post-thumbnail

이벤트 드리븐 프로그래밍

애플리케이션이 특정 타입의 이벤트에 반응하여 어떤 일을 하고 싶다면 해당하는 타입의 이벤트가 발생했을 때 호출될 함수를 브라우저에게 알려 호출을 위임해주어야 합니다.

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

즉, 사용자가 명시적으로 함수(이벤트 핸들러)를 호출하는 것이 아니라 브라우저에게 함수 호출을 위임(이벤트 핸들러 등록)하는 것입니다.

이벤트와 그에 대응하는 함수(이벤트 핸들러)를 통해 사용자와 애플리케이션은 상호작용을 할 수 있습니다. 이와 같이 프로그램의 흐름을 이벤트 중심으로 제어하는 프로그래밍 방식을 이벤트 드리블 프로그래밍이라고 합니다.

이벤트 타입

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

마우스 이벤트

click : 마우스 버튼을 클릭했을 때 발생

dbclick : 마우스 버튼을 두 번 클릭했을 때 발생

mousedown : 마우스 버튼을 눌렀을 때 발생

mouseup : 눌려있던 마우스 버튼을 놓았을 때 발생

mousemove : 마우스 커서를 움직였을 때 발생

mouseover : 마우스 커서가 HTML 요소 내부로 이동했을 때(버블링 됨)

mouseout : 마우스 커서가 HTML 요소 밖으로 이동했을 때(버블링 됨)

mouseenter : 마우스 커서가 HTML 요소 내부로 이동했을 때(버블링 되지 않음)

mouseleave : 마우스 커서가 HTML 요소 밖으로 이동했을 때(버블링 되지 않음)

키보드 이벤트

keydown : 모든 키를 눌렀을 때 발생

keypress : 문자 키를 눌렀을 때 연속적으로 발생

keyup : 누르고 있던 키를 놓았을 때 한 번만 발생

포커스 이벤트

focusin : HTML 요소가 포커스를 받았을 때 발생(버블링 됨)

focusout : HTML 요소가 포커스를 잃었을 때 발생(버블링 됨)

focus : HTML 요소가 포커스를 받았을 때 발생(버블링 되지 않음)

blur : HTML 요소가 포커스를 잃었을 때 발생(버블링 되지 않음)

폼 이벤트

submit : form 요소 내에서 submit 버튼을 눌렀을 때 발생

reset : from 요소 내에서 reset 버튼을 눌렀을 때 발생

값 변경 이벤트

input : input(text, radio, checkbox), textarea, select 요소에 값이 입력되었을 때 발생

change : input(text, radio, checkbox), textarea, select 요소의 값이 변경되었을 때
(change 이벤트는 HTML 요소가 포커스를 잃었을 때 사용자의 입력이 완료되었다고 인식하여 발생합니다.)

readystatechange : HTML 문서의 로드와 파싱 상태를 나타내는 document.readyState 값이 변경될 때 발생

DOM 뮤테이션 이벤트

DOMContentLoaded : HTML 문서의 파싱이 완료되어 DOM 생성이 완료되었을때 발생

뷰 이벤트

resize : 브라우저의 윈도우의 크기를 리사이즈할 때 연속적으로 발생한다(오직 window 객체에서만 발생)

scroll : 웹 페이지나 HTML 요소를 스크롤할 때 연속적으로 발생

리소스 이벤트

load : 모든 리소스의 로드가 완료되었을 때 발생(주로 window 객체에서 발생)

unload : 리소스가 언로드될 때(주로 새로운 웹페이지를 요청한 경우 발생)

abort : 리소스 로드를 중단할 때 발생

error : 리소스 로드가 실패했을 때

이벤트 핸들러 등록

이벤트 핸들러는 이벤트가 발생했을 때 브라우저에게 호출을 위임할 함수입니다. 이벤트 핸들러 등록은 이벤트가 발생했을 때 이벤트 핸들러를 브라우저에게 호출을 위임하는 것을 말합니다.

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

HTML 요소의 어트리뷰트 중에서 이벤트에 대응하는 이벤트 핸들러 어트리뷰트가 존재합니다. 이벤트 핸들러 어트리뷰트는 'on 접두사 + 이벤트 타입' 으로 이루어져 있습니다.

이벤트 핸들러 어트리뷰트의 값으로 함수 호출문 같은 문을 할당하면 이벤트 핸들러가 등록됩니다.

<button onclick="sayHello('kim');">click</button>

이벤트 핸들러 어트리뷰트의 값암묵적으로 생성될 이벤트 핸들러의 함수 몸체입니다. 즉, 아래와 같은 함수(이벤트 핸들러)가 암묵적으로 생성이 되고 이벤트 핸들러 어트리뷰트와 같은 이름의 이벤트 핸들러 프로퍼티에 해당 함수를 할당합니다.

function onclick(event) {  // 실제 이벤트 핸들러
	sayHello('kim');  // 이벤트 핸들러 어트리뷰트에 할당한 값
}

이처럼 함수 참조 대신 함수 호출문을 할당하는 이유는 인수를 전달하기 위해서입니다.

이벤트 핸들러 어트리뷰트에 할당한 값이 이벤트 핸들러의 함수 몸체이므로 여러 개의 문을 할당할 수 있습니다.

<button onclick="sayHello('kim'); console.log('Hello, World!'); console.log('clicked!);">click</button>

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

window, Document, HTMLElement 타입의 DOM 노드 객체는 이벤트에 대응하는 이벤트 핸들러 프로퍼티가 존재합니다.

이벤트 핸들러 프로퍼티 키는 'on 접두사 + 이벤트 타입' 으로 이루어져 있으며, 이벤트 핸들러 프로퍼티에 함수를 바인딩하면 이벤트 핸들러가 등록됩니다.

$button.onclick = (event) => console.log('clicked!');

이벤트 핸들러를 등록하기 위해서는 이벤트를 발생시킨 객체인 이벤트 타깃과 이벤트 종류를 나타내는 이벤트 타입 그리고 이벤트 핸들러를 지정해야 합니다. 이벤트 핸들러는 대부분 이벤트를 발생시킬 이벤트 타깃에 바인딩하지만 반드시 그런것은 아닙니다. 이벤트 핸들러는 이벤트 타깃 또는 이벤트를 캐치할 DOM 노드 객체에 바인딩합니다.
이벤트 핸들러 프로퍼티 방식은 하나의 이벤트 핸들러만을 바인딩할 수 있습니다.

EventTarget.prototype.addEventListener 방식

이벤트타깃.addEventListener('이벤트 타입', 이벤트 핸들러 [,capture 사용 여부]);
  1. addEventListener 메서드의 첫 번째 인수이벤트 타입을 전달합니다. 이때 on 접두사는 붙이지 않습니다.

  2. 두 번째 인수로는 등록할 이벤트 핸들러를 전달합니다.

  3. 세 번째 인수로는 이벤트를 캐치할 이벤트 전파 단계를 지정합니다. true를 지정하면 캡처링 단계에서 이벤트를 캐치하고, false나 생략시 타깃 단계 또는 버블링 단계에서 이벤트를 캐치합니다.

$button.addEventListener('click', function () {
    console.log('clicked!');
    });

addEventListener 방식은 여러 개의 이벤트 핸들러를 등록할 수 있습니다.

단, 동일한 이벤트 핸들러의 참조를 중복해서 등록시 하나의 이벤트 핸들러만 등록됩니다.

이벤트 핸들러 제거

addEventListener 메서드로 등록한 이벤트 핸들러는 removeEventListener로 제거합니다. 이때 전달할 인수는 addEventListener와 동일합니다.

만약 이벤트 핸들러로 무명 함수를 등록했다면 제거할 수 없습니다. 그러므로 이벤트 핸들러를 변수나 다른 자료 구조에 담아서 등록하는 것을 권장합니다.

// 이벤트 핸들러 등록
$button.addEventListener('click', sayHello);

// 이벤트 핸들러 제거
$button.removeEventListener('click', sayHello);

기명 함수를 등록한 경우 이벤트 핸들러 내부에서 removeEventListener 메서드를 호출하여 제거할 수 있습니다. 이때 이벤트는 단 한 번만 발생합니다.

이벤트 핸들러 어트리뷰트와 프로퍼티 방식으로 등록한 이벤트 핸들러는 이벤트 핸들러 프로퍼티에 null을 할당하여 이벤트 핸들러를 제거할 수 있습니다.

$button.onclick = null;

이벤트 객체

이벤트가 발생하면 이벤트에 관련된 다양한 정보를 갖고 있는 이벤트 객체가 동적으로 생성됩니다. 생성된 이벤트 객체는 암묵적으로 이벤트 핸들러의 첫 번째 인수로 전달됩니다.

이는 브라우저가 이벤트 핸들러를 호출할 때 이벤트 객체를 인수로 전달하기 때문입니다.

이벤트 핸들러 어트리뷰트의 경우 암묵적으로 생성될 이벤트 핸들러의 첫 번째 매개변수가 event로 명명됩니다. 따라서 이벤트 객체를 참조하려면 이벤트 핸들러 어트리뷰트 값으로 할당한 함수 호출문에 event라는 식별자로 이벤트 객체를 참조할 수 있습니다.

이벤트 객체 상속 구조

이벤트가 발생하면 이벤트 타입에 따라 이벤트 객체가 생성됩니다. 이벤트 객체도 상속 구조를 갖습니다.

Event, UIEvnet, MouseEvent 모두 생성자 함수입니다. new 연산자와 함께 호출하여 이벤트 객체를 생성할 수 있습니다. 이벤트 발생시 암묵적으로 생성되는 이벤트 객체 또한 생성자 함수로 생성됩니다.

Event 인터페이스에는 모든 이벤트 객체가 갖고 있는 공통의 프로퍼티가 정의되어 있고 모든 이벤트 객체가 상속을 받습니다.

Event.prototype의 프로퍼티(이벤트 객체의 공통 프로퍼티)

  • type : 이벤트 타입 (string)

  • target : 이벤트를 발생시킨 DOM 요소 (DOM 요소 노드)

  • currenTarget : 이벤트 핸들러가 등록된 DOM 요소 (DOM 요소 노드)

  • eventPhase : 이벤트 전파 단계 (Number)
    (0: 이벤트 없음, 1 : 캡처링 단계, 2 : 타깃 단계, 3 : 버블링 단계)

  • bubbles : 이벤트가 버블링으로 전파하는지 여부 (Boolean)

  • cancelable : preventDefualt로 기본 동작을 취소할 수 있는지 여부 (Boolean)

  • defaultPrevented : preventDefault를 호출하였는지 여부 (Boolean)

  • isTrusted : 사용자 행위로 생성된 이벤트 객체인지 여부 (Boolean)

  • timeStamp : 이벤트가 발생한 시각. 1970/01/01/00:00:0(UTC)부터 경과한 밀리초 (Number)

이벤트 전파

DOM 트리상에 존재하는 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파됩니다. 이를 이벤트 전파라고 합니다.

이벤트가 발생하여 암묵적으로 생성된 이벤트 객체는 이벤트를 발생시킨 DOM 요소 노드를 중심으로 DOM 트리를 통해 전파됩니다.
이벤트 전파는 총 3단계로 구분할 수 있습니다.

이벤트 전파
  1. 캡처링 단계 : 이벤트 객체가 상위 요소에서 하위 요소 방향으로 전파

  2. 타깃 단계 : 이벤트 객체가 이벤트 타깃에 도달

  3. 버블링 단계 : 이벤트 객체가 하위 요소에서 상위 요소 방향으로 전파

이벤트가 발생하면 이벤트 객체는 window에서 시작하여 이벤트 타깃 방향으로 전파된다. 이것이 캡처링 단계이다(window -> event.target).

이벤트 객체가 이벤트 타깃에 도달하면 이것이 타깃 단계이다(event.target).

이벤트 객체가 이벤트 타깃에서 window 방향으로 전파된다. 이것이 버블링 단계이다(event.target -> window).

이벤트 핸들러 어트리뷰트와 프로퍼티 방식타깃 단계버블링 단계의 이벤트만 캐치할 수 있다. 하지만 addEventListener 방식은 타깃 단계, 버블링 단계뿐만 아니라 캡처링 단계의 이벤트로 캐치할 수 있다. addEventListener 메서드이 세 번째 인수로 true를 전달하면 캡처링 단계에서 이벤트를 캐치할 수 있다.

이벤트는 이벤트를 발생시킨 이벤트 타깃은 물론이고 상위 DOM 요소에서도 캐치할 수 있다. DOM 트리를 통해 전파되는 이벤트 객체는 이벤트 패스(이벤트 객체가 통과하는 DOM 트리 상의 경로, Event.prototype.composedPath 메서드로 확인이 가능)에 위치한 모든 DOM 요소에서 캐치할 수 있다.

이벤트 위임

이벤트 위임은 여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하지 않고 하나의 상위 DOM 요소에 이벤트 핸들러를 등록하는 것을 의미합니다.

이벤트는 이벤트 타깃뿐만 아니라 상위 DOM 요소에서 캐치할 수 있습니다.
이벤트 위임을 통해 이벤트 핸들러를 상위 DOM 요소에 등록하면 여러 개의 하위 DOM 요소에 개별적으로 이벤트 핸들러를 등록하지 않아도 되며, 이후 추가되는 하위 DOM 요소에도 이벤트 핸들러를 등록할 필요가 없습니다.

주의해야할 점은 이벤트를 발생시킨 모든 하위 DOM 요소에 이벤트 핸들러가 호출이 되므로 이벤트 핸들러가 호출이될 요소를 한정하기 위해서 이벤트 타깃을 검사할 필요가 있습니다. 이때 Element.prototype.matches 메서드를 통해 반응할 DOM 요소를 한정할 수 있습니다.

function (event) {
	// 이벤트 타깃이 .fruits의 자식 요소 li가 아니면 무시한다.
    if (!event.target.matches('.fruits > li')) return;
    ,,,
}

DOM 요소의 기본 동작 조작

DOM 요소의 기본 동작 중단

DOM 요소는 저마다 기본 동작을 갖고 있습니다.
이벤트 객체의 Event.prototype.preventDefault 메서드는 DOM 요소의 기본 동작을 중단시킵니다.

이벤트 전파 방지

이벤트 객체의 Event.prototype.stopPropagation 메서드는 이벤트 전파를 중단시킵니다. 이는 상위 DOM 요소에 이벤트 위임을 한 상태에서 하위 DOM 요소가 자체적으로 이벤트를 처리하고 싶을 때 사용합니다.

자신이 발생시킨 이벤트가 전파되는 것을 중단하여 자신에게 바인딩된 이벤트 핸내들러만 실행되도록할 때 사용합니다.

이벤트 핸들러 내부의 this

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

이벤트 핸들러 어트리뷰트에 할당한 값은 암묵적으로 생성될 이벤트 핸들러의 함수 몸체이므로 할당한 값에서 this를 참조하면 전역 객체를 가리키게 됩니다.

<button onclick="clickMe()">Click</button>

function clickMe() {
    console.log(this); // window
}

아래와 같은 이벤트 핸들러가 암묵적으로 생성되고 onclick 이벤트 핸들러 프로퍼티에 바인딩된다. onclick 이벤트 핸들러 어트리뷰트에 할당한 값은 아래와 같이 생성된 이벤트 핸들러 몸체로서 포함됩니다.

function onclick(event) {
    clickMe();  // 일반 함수로 호출됨, this 바인딩은 전역 객체를 가리킨다.
}

그 이유는 이벤트 핸들러 어트리뷰트에 할당한 함수 호출문은 일반 함수로 호출되기 때문이다. 일반 함수로 호출되는 함수 내부 this는 언제나 전역 객체를 가리킵니다.

단, 이벤트 핸들러를 호출할 때 인수로 전달한 this이벤트 핸들러를 바인딩한 DOM 요소 노드를 가리킵니다.

<button onclick="clickMe(this)">Click</button>

function clickMe(button) {
    console.log(button);  // 이벤트 핸들러를 바인딩한 button 요소
    console.log(this);  // window
}

아래와 같은 이벤트 핸들러가 생성되고 onclick 이벤트 핸들러 프로퍼티에 바인딩된다.

function onclick(event) {
    clickMe(this);    // this는 암묵적으로 생성된 이벤트 핸들러 내부의 this를 참조하기 때문에 이벤트 핸들러가 등록된 DOM 요소 노드인 button 노드를 전달하면서 호출된다.
}

clickMe 함수에 전달한 this는 암묵적으로 생성된 이벤트 핸들러 내부의 this이다. 즉, 이벤트 핸들러 내부의 this는 이벤트를 바인딩한 DOM 요소를 가리킵니다. 이는 이벤트 핸들러 프로퍼티 방식에서도 동일하게 동작합니다.

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

이벤트 핸들러 프로퍼티 방식으로 등록한 이벤트 핸들러 내부의 this는 이벤트 핸들러를 등록한 DOM 요소를 가리킵니다. 즉, event.currentTarget과 동일한 DOM 요소를 가리킵니다.

addEventListener 방식

addEventListener 방식으로 등록한 이벤트 핸들러 내부의 this 또한 이벤트 핸들러를 등록한 DOM 요소를 가리킵니다.

즉, 이벤트가 발생해서 호출되는 이벤트 핸들러 내부의 this는 언제나 이벤트 핸들러가 등록된 DOM 요소를 가리킨다. 단, 이벤트 핸들러가 화살표 함수인 경우 화살표 함수는 자체적으로 this 바인딩을 갖지 않고 외부의 this 바인딩을 참조합니다.

이벤트 핸들러에 인수 전달

  1. 이벤트 핸들러 내부에서 인수를 전달하면서 함수를 호출하는 방법을 통해 인수를 전달할 수 있다.

  2. 이벤트 핸들러를 반환하는 함수인수를 전달한 호출문을 이벤트 핸들러로 등록한다.

커스텀 이벤트

커스텀 이벤트 생성

이벤트 객체는 Event, MouseEvent, FocusEvent이벤트 생성자 함수로 이벤트 객체를 생성할 수 있습니다. 이처럼 개발자 의도로 생성된 이벤트 객체를 커스텀 이벤트 객체라고 합니다.

이벤트 생성자 함수 첫 번째 인수로는 이벤트 타입을 전달합니다. 이때 전달할 이벤트 타입은 기존의 이벤트를 전달할 수 있고, 기존 이벤트 타입이 아닌 임의의 이벤트 타입을 전달할 수있습니다. 생성된 커스텀 이벤트 객체는 bubblescancelable이 false로 기본 설정이 됩니다.

만약 이를 true로 고치고 싶다면 두 번째 인수로 { bubbles: ture, cancelable: true }를 전달합니다. bubbles, cancelable 뿐만 아니라 다른 프로퍼티 또한 값을 지정할 수 있습니다.

이벤트 생성자 함수로 생성한 커스텀 이벤트는 isTrusted의 값이 항상 false이며 수정할 수 없습니다.

커스텀 이벤트 디스패치

생성된 커스텀 이벤트는 EventTarget.prototype.dispatchEvent 메서드를 통해 디스패치할 수 있습니다. 인수로 이벤트 객체를 전달하면서 호출하면 인수로 전달한 이벤트 타입의 이벤트가 발생합니다.
dispatchEvent 메서드는 이벤트 핸들러를 동기 처리 방식으로 호출합니다. 즉, 이벤트 핸들러를 직접 호출하는 것과 같습니다.

기존 이벤트 타입이 아닌 임의의 이벤트 타입을 지정하여 이벤트 객체를 생성한 경우 일반적으로 CustomEvent 생성자 함수를 통해 생성하고, 두 번째 인수이벤트와 함께 전달할 정보detail 프로퍼티에 객체로 전달할 수있다.

기존 이벤트가 아닌 임의의 이벤트 타입으로 생성한 경우 이벤트 핸들러를 등록할 때 addEventListener 방식으로만 등록할 수 있다. 그 이유는 이벤트 핸들러 어트리뷰트 / 프로퍼티 방식은 'on + 이벤트 타입'으로 이루어진 어트리뷰트나 프로퍼티가 요소에 존재하지 않기 때문이다.

profile
Frontend Dev

0개의 댓글