성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Javascript

이벤트, 그리고 버블링과 캡처링

제이콥(JACOB) 2020. 8. 13. 22:54

이벤트란?

 events are actions or occurrences that happen in the system you are programming.

 이벤트는 프로그래밍 중인 시스템에서 발생하는 동작(actions) 또는 발생(occurrences)이며, 동기식 혹은 비동기식으로 전달된다. 시스템은 이벤트가 발생할 때 어떤 종류의 신호를 생성(또는 발동)하고 어떤 종류의 동작이 수행될 수 있는 메커니즘을 제공한다. 이벤트가 발생하면 자동으로 일부 코드가 실행되며 동작한다. 

이벤트 핸들러

 이벤트 핸들러(event handler)란, 이벤트가 발생할 때 실행되는 함수를 말하며, 이벤트 리스너(event listeners)라고도 불린다. 하지만 엄밀히 말하면 이벤트 리스너는 이벤트 발생을 수신하며, 이벤트 핸들러는 이벤트 발생에 대한 응답으로 실행되는 코드를 말한다.

 그리고 이벤트 발생에 대한 응답으로 실행되도록 정의된 경우 이벤트 핸들러를 등록(registering an event handler)한다고 한다.

 버튼을 누를 때마다 button의 onclick 속성을 통해 이벤트 핸들러가 호출되고 있다. 여기서 최신 이벤트 메커니즘으로 위 코드를 변경한다면 아래와 같이 바꿀 수 있다. 

// btn.onclick = handleBgChangeClick; // 예전 방식
btn.addEventListener("click", handleBgChangeClick); // DOM Level 2 Events Specification

 바뀐 메커니즘의 경우 앞의 방식보다 몇 가지 장점을 더 가지고 있다. 

 

1. removeEventListener()이라는 리스너를 제거하는 대응 함수가 있다. 

btn.removeEventListener("click", handleBgChangeClick);

2. 동일한 리스너에 여러 핸들러를 등록할 수 있다.

// functionA 핸들러를 functionB 핸들러가 덮어씀
btn.onclick = functionA;
btn.onclick = functionB;
// 두 핸들러 모두 실행됨
btn.addEventListener('click', functionA);
btn.addEventListener('click', functionB);

이 외에도 여러 강력한 옵션을 제공한다. 나중에 차차 살펴보자.


쉬어가기: DOM이란?

 DOM (Document Object Model)은 유효한 HTML 및 올바른 형식의 XML 문서를 위한 API이며, 문서(Document)의 논리적 구조와 문서에 접근하고 조작하는 방법을 정의한다. 프로그래머는 DOM을 사용하여 문서를 작성하고, 구조를 탐색하고, 요소와 내용을 추가, 수정, 삭제할 수 있다. 

 또한 DOM은 모든 프로그래밍 언어와 다양한 환경에서 사용할 수 있도록 표준 프로그래밍 인터페이스를 제공하는 것을 지향하고 있다.

출처: w3.org

 

DOM 아키텍처

출처: w3.org


 참고로 인라인 이벤트 핸들러를 사용하는 것은 나쁜 습관으로 분류된다. 여기서 말하는 인라인 이벤트 핸들러는 아래와 같이 코드를 작성하는 경우를 말한다.

<button onclick="alert('이러지 말라구!');">왜 그러는겨</button>

 이렇게 사용하지 않는 이유는 HTML과 JavaScript를 혼용해서 사용하는 것이 좋지 않기 때문이다. 구문 분석 자체가 어려워지고, 해당 JavaScript 코드의 재사용성도 불가능하기 때문이다.  또한 단일 파일이라도, 버튼이 여러 개라면 버튼 수많음 해당 속성을 추가해야 되기 때문에 유지보수에 안 좋다. 

 

 참고) 만약 React를 사용한다면, 인라인 함수 사용 시 

(() => null) === (() => null) // false

위와 같은 결과가 나오기 때문에 render 실행시마다 함수 인스턴스가 새로 생성되며, 항상 re-render가 된다. 

 

이벤트 종류

 

Event reference

DOM Events are sent to notify code of interesting things that have taken place. Each event is represented by an object which is based on the Event interface, and may have additional custom fields and/or functions used to get additional information about wh

developer.mozilla.org

 

이벤트 객체

 이벤트 객체(Events objects)는 추가 기능과 정보를 제공하기 위해 이벤트 핸들러 함수에 자동으로 전달이 된다. 위에 예제에서 작성한 코드를 약간만 수정해 보자.

 이벤트 객체가 이벤트 핸들러에 전달된 것을 확인할 수 있다. 이 이벤트 객체에는 많은 속성들이 있다. 몇 가지만 짚고 넘어가 보자.

 

event.preventDefault()

 이벤트가 기본적으로 수행하는 작업을 수행하지 못하도록 방지하는 상황에서 사용한다. 가장 대표적인 경우로는 form을 예로 들 수 있다. 

// MDN 예제

const form = document.querySelector('form');
const fname = document.getElementById('fname');
const lname = document.getElementById('lname');
const para = document.querySelector('p');

form.onsubmit = function(e) {
  if (fname.value === '' || lname.value === '') {
    e.preventDefault();
    para.textContent = 'You need to fill in both names!';
  }
}

 

event.stopPropagation()

 이벤트 캡처 및 버블링 단계에서 현재 이벤트의 추가 전파를 막는다. 하지만, 기본 동작이 발생하는 걸 막지는 않으므로 이벤트 동작은 계속 처리된다(동작을 중지하는 건 preventDefault()). 


출처: w3.org

 위 그림은 이벤트가 DOM Tree를 통해 전파되는 방법을 설명한다. 프로그램은 dispatchEvent() 메서드를 사용하여 이벤트 개체를 전달할 수 있으며, 이벤트 객체는 DOM 이벤트 흐름에 의해 결정된 대로 DOM Tree를 통해 전파된다.

출처: MDN

event.target vs event.currentTarget

- event.target: 이벤트를 시작한 "대상"요소이며, 버블링을 통해 변경되지 않는다.

- event.currentTarget: "현재" 요소이며, 현재 실행중인 핸들러가 있는 요소이다.

 

이벤트 버블링

 자기 자신(currentTarget)에서 부모로 전파되는 것을 이벤트 버블링(event bubbling)이라고 한다.

최신 브라우저에서는 기본적으로 "거의" 모든 이벤트 핸들러가 버블링 단계에 등록된다. 위 다이어 그램을 예로 보자면, 비디오를 클릭하면 클릭 이벤트가 <video> 요소에서 <html> 쪽으로 버블링 된다. 

 만약 이벤트 버블링을 원치 않는다면,  event의 stopPropagtion() 메서드를 통해 이벤트가 더 이상 체인 위로 버블링되지 않도록 할 수 있다.

// mdn 예제
video.onclick = function(e) {
  e.stopPropagation();
  video.play();
};

 참고로 focus 이벤트처럼 버블링되지 않는 경우도 있다.

이벤트 버블링

 실제로 구현해 봐도 이벤트 버블링이 기본적으로 설정돼 있음을 알 수 있다. 

function handleInnerClick(event) {
  event.stopPropagation();
  alert("안이에요!");
}

 event.stopPropagation()를 통해 이벤트 버블링을 막는다면 이제 더 이상 버블링이 일어나지 않으므로 handleOuterClick 핸들러는 동작하지 않는다.

이벤트 버블링 막기

이벤트 캡쳐링

 부모에서 자기 자신에게 이벤트가 전파되면 이벤트 캡쳐링(event capturing)이라고 한다. 만약 캡쳐링을 설정하고 싶다면 addEventListener의 세 번째 인자로 true를 주면 된다.

outerContainer.addEventListener("click", handleOuterClick, true);
innnerContainer.addEventListener("click", handleInnerClick, true);

그다음 다시 실행을 해보면 부모의 이벤트가 먼저 실행됨을 알 수 있다. 

 여기에서 중요한 포인트는 "부모"에게서 전달되는 이벤트이다. 다른 형제의 자손을 대상으로 하는 이벤트의 가로채기는 허용되지 않으며 캡처링된 EventTarget의 후손을 대상으로 하는 이벤트의 가로채기만 허용한다. 또한 이벤트 캡처는 단일 EventTarget에 지정되지 않고 특정 유형의 이벤트에 대해 지정된다. 즉 이벤트 캡처가 지정되면 캡처자의 하위 항목을 대상으로 지정된 유형의 모든 이벤트를 가로채게 된다. 

 캡처링된 이벤트 리스너의 추가 처리를 방지하고 싶다면 event의 stopPropagtion() 메서드를 호출하면 된다. 

 

만약 이벤트 캡쳐링과 이벤트 버블링의 이벤트 핸들러가 모두 있는 경우에는?

 캡쳐링 단계가 먼저 실행된 다음 버블링 단계가 실행된다.

 

 

Event bubbling and Capturing - CodeSandbox

Event bubbling and Capturing by JungKyuHyun

codesandbox.io

 

반응형