자바 스크립트 의 대부분의 코드는 이벤트(event)에 의해 동작된다. 이벤트란 클릭 이라던가 키 입력 같이 사용자가 행하는 모든 동작을 의미하는데, 이러한 이벤트가 발생했을 때 이에 맞는 상황을 지정 해주고 처리 하는 것을 이벤트 핸들링이라 한다.
사용자가 버튼을 클릭하면 (click 이벤트 가 발생하면) userEvent 함수가 실행되는 간단한 코드이다. 버튼을 클릭하고 나면 userEvent 함수가 실행되고 userEvent 함수에 event 인자가 넘어오게 된다. 넘어온 event 인자를 콘솔에 출력 해보면 이벤트와 관련된 정보를 확인할 수 있다.
여기서 버튼 요소와 click 이벤트를 연결할 때 사용한 addEventListener() 웹 API는 개발자들이 화면에 동적인 기능을 추가 하고 싶을 때 사용하는 기능이며 사용자의 입력에 따라 여러 동작을 구현할 수 있다.
물론 이렇게 DOM 요소에 addEventListener 메서드를 호출하여 바인딩 하는 방법 외에도 요소와 이벤트를 연결하는 여러가지 방법들이 있지만, 오늘의 주제는 이벤트 버블링 이기에, 다른 방법들 까지는 다루지 않겠다.
.
이제 본격적으로 중점에 들어가기 앞서 브라우저가 이벤트를 감지하는 방식엔 2가지가 있다는 것을 알아야 한다.
element.addEventListener(event, function, useCapture)
위 처럼 addEventListener()
를 사용할 때, 세번째 인자인 useCapture 와 관련된 개념이 바로 이벤트 버블링(Bubbling) 과 캡쳐링(Capturing) 이다. default 값은 false (버블링)이며, true로 변경 시 캡쳐링을 통해 이벤트를 전파한다. 자세한 내용은 MDN문서에서 찾아볼 수 있다.
이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 요소들로 전달되어 가는 특성을 의미한다. 쉽게 말하자면 HTML은 기본적으로 트리 구조를 갖는데 트리 구조상 더 위에 위치한 것이 상위 요소 이므로 하위요소 즉, 자식 요소에서 발생한 이벤트가 상위의 부모 요소에 까지 영향을 미치는 것을 의미한다.
<body>
<div class="one">
<button>one</button>
<div class="two">
<button>two</button>
<div class="three">
<button>three</button>
</div>
</div>
</div>
</body>
<script>
const divs = document.querySelectorAll("div");
divs.forEach((div) => {
div.addEventListener("click", () => console.log(div.className));
});
</script>
세 개의 div태그와 각 div 태그 마다 식별을 위해 button태그를 넣어 주었다. 각 div 태그에 모두 클릭 이벤트를 바인딩 해 주었으며, 클릭 시 해당 태그의 className을 콘솔을 출력 해주도록 해주었다.
우린 여기서 한가지 의문을 가질 수 있다. 어째서 'three'버튼을 클릭하면 'three, two, one' 모두가 출력되는 걸까?
그 이유는 브라우저가 이벤트를 이벤트 버블링 방식으로 감지했기 때문이다. 이벤트 버블링은 특정 요소에서 이벤트 발생 시 해당 이벤트를 최 상위에 있는 요소까지 전파 시키는 방식이다. 따라서 'three'버튼 클릭 시 'three' ⇒ 'two' ⇒ 'one' 순으로 클릭 이벤트가 동작하며, 같은 이유로 'two' 버튼 클릭 시 'two' ⇒ 'one' 으로 클릭 이벤트가 전파되는 것이다 .
위 예제 같은 경우, 각 상위 하위 요소 모두 이벤트가 등록되어 있기 때문에 이벤트 흐름을 직접적으로 감지할 수 있지만, 특정 요소에 만 이벤트가 등록되어 있는 상황이었다면, 그리고 이러한 전파 방식을 예측하지 못했다면, 아마 골치 아픈 상황이 생겼을 것이다.
이벤트 캡쳐링이란 이벤트 버블링과는 반대로 상위요소에서 하위요소로의 이벤트 전파방식 을 의미한다.
특정 이벤트가 발생했을 때 최상위 요소인 body 태그에서 부터 해당 태그를 찾아 내려간다.
<body>
<div class="one">
<button>one</button>
<div class="two">
<button>two</button>
<div class="three">
<button>three</button>
</div>
</div>
</div>
</body>
<script>
const divs = document.querySelectorAll("div");
divs.forEach((div) => {
div.addEventListener("click", () => console.log(div.className), true);
}); //addEventListener()의 세번째 인자로 true를 주었다. 기본값은 false(버블링)이다.
</script>
이벤트 버블링과 비슷한 결과지만, 이벤트 버블링과는 반대로 위에서부터 아래로 전파되는 방식이기에, 'three' ⇒ 'two' ⇒ 'one'이 아닌, 'one' ⇒ 'two' ⇒ 'three' 순으로 출력되게된다.
자 그럼 지금까지 이벤트 버블링과 캡쳐링에 대해 알아보았다. 그런데 버블링과 캡쳐링의 특징들만 생각해보면 왜 굳이 이렇게 만들어놨으며 이런 특성들을 어떻게 활용해야하는지 감이 잘 잡히지 않는다. 하지만 하위 태그에 이벤트가 발생하더라도, 상위 태그에 이벤트 등록이 되어 있으면 클릭 이벤트가 발생한 하위 태그에서 차례대로 상위 태그까지 이벤트가 발생되는 이벤트 버블링의 특성을 이용하면 좀더 효율적인 코드 작성이 가능해진다.
만일 <ul>
태그가 상위 요소이고, 하위 요소들로 <li>
태그들이 있다고 생각해보자. 그리고 각 <li>
태그들을 눌렀을 때 각 요소들에 각각의 이벤트를 부여하고 싶다면 일일이 addEventListener
를 부여하는 방법이 있지만, 이는 너무 비효율이며, 특정 상황에서 <li>
태그가 새로 생겨야 하는 상황이라면 이를 처리할 방법은 더더욱 복잡해진다. 그러나 이런 상황에서 이벤트 버블링의 특성을 활용하면 효율적인 이벤트 처리가 가능해진다.
<body>
<ul class="lists">
<li class="1">list1</li> // 클릭 시 <li class="4"></li>
<li class="2">list2</li> // 클릭 시 <li class="4"></li>
<li class="3">list3</li> // 클릭 시 <li class="4"></li>
<li class="4">list4</li> // 클릭 시 <li class="4"></li>
</ul>
</body>
<script>
const lists = document.querySelector('.lists')
lists.addEventListener('click',(event)=> console.log(event.target))
</script>
위 예제처럼 상위 요소인 <ul>
태그에 이벤트 리스너를 등록해주면 하위요소에서 발생한 이벤트가 위로 전파되는 이벤트 버블링 특성에 의해 다른 <li>
태그들이 생겨도 정상적으로 이벤트 핸들링을 할 수 있다. 그리고 이를 이벤트 위임이라 한다.