[Study/JavaScript] 이벤트 버블링 / 캡처링 / 이벤트 위임

SoShy·2024년 1월 19일

JavaScript_Study

목록 보기
23/36
post-thumbnail

1. 이벤트 버블링

하나의 요소에 이벤트가 발생하게 되면, 이 요소에 할당된 이벤트 핸들러가 동작하고 끝나는 게 아니라, 부모 요소의 핸들러도 함께 동작을 하게 되는데, 이를 이벤트 버블링이라고 한다.

이 과정은 최상단의 window 객체를 만날 때까지 반복된다.

다만, 이벤트 버블링이 일어나도, 이벤트 객체의 target 프로퍼티는 변하지 않고, 처음 이벤트가 발생한 시작점을 담고 있다.

이를 이용하면, 최초의 이벤트가 발생한 위치를 정확하게 파악할 수 있다.

만약, 이벤트 핸들러가 등록된 요소에 접근하고 싶다면, currentTarget 프로퍼티를 활용하면 된다.

이를 활용하면, 실제로 이벤트 핸들러가 동작하는 요소를 확인할 수 있다.

자주 사용되지는 않지만, 알아두면 좋다.

이벤트 버블링을 멈추고 싶다면, event 객체의 stopPropagation method를 사용하여 해결할 수 있다.

다만, 정말 필요한 경우가 아니라면, 버블링을 막는 일은 하지 않는 것이 좋다.

버블링을 막는다는 것은 범위를 제한하는 것이기 때문에, 원하는 대로 동작하지 않는 상황이 발생할 수 있다.

2. 캡처링 (capturing)

표준 DOM 이벤트에서 정의한 이벤트 흐름에는 3가지 단계가 존재한다.

  • 캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계

  • 타깃 단계 : 이벤트가 실제 타깃 요소에 전달되는 단계

  • 버블링 단계 : 이벤트가 상위 요소로 전파되는 단계

타깃 단계는 이벤트 객체의 target 프로퍼티가 되는 요소에 등록되어 있는 이벤트 핸들러가 동작하는 단계로, 가장 처음 이벤트 핸들러가 동작하게 되는 순간을 의미한다.

캡처링은 이벤트가 발생하면 가장 먼저, 버블링의 반대 방향으로 진행되는 이벤트 전파 방식이다.

아래 예시에서, 이벤트가 발생하면, window 객체에서부터 target까지 이벤트 전파가 일어나며, 이 단계가 캡처링 단계이다. 타깃에 도달하게 되면 타깃에 등록된 이벤트 핸들러가 동작하는데, 이 단계가 타깃 단계이다. 이후 다시 target 에서 window 객체로 이벤트가 전파되는데, 이 단계를 버블링 단계라고 한다.

보통 타깃 단계에서 target에 등록된 이벤트 핸들러가 있으면, 해당 이벤트 핸들러가 먼저 동작한 이후에, 버블링 단계에서 각 부모 요소에 등록된 이벤트 핸들러가 있을 경우 그 때 해당 이벤트 핸들러가 동작하는 것이 일반적이다.

때문에, 캡처링 단계에서 이벤트를 발생시켜야 하는 일은 매우 드물다.

하지만, 상황에 따라, 캡처링 단계에서 부모 요소의 이벤트 핸들러를 동작시켜야 할 수도 있다.

이 경우에는 addEventListener의 세 번째 프로퍼티에 true 또는 {capture:true}를 전달하면 된다.

3. 이벤트 위임

자식 요소에서 발생하는 이벤트를 부모 요소에서 다루는 방식을 이벤트 위임이라고 한다.

아래 예시처럼, 자바스크립트 코드를 작성했다고 해보자.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="style.css">
  <title>JS with Codeit</title>
</head>
<body>
  <div id="content">
    <h1 id="title">오늘 할 일</h1>
    <ul id="list">
      <li class="item">자바스크립트 공부</li>
      <li class="item">유튜브 시청</li>
      <li class="item">저녁 약속</li>
      <li class="item">독서</li>
    </ul>
  </div>
  <script src="index.js"></script>
</body>
</html>
const list = document.querySelector('#list');

for (let item of list.children) {
  item.addEventListener('click', function(e) {
    e.target.classList.toggle('done');
  })
}

만약, 여기서 아래와 같이 코드를 수정한다면, 새롭게 생성된 항목에 대해서는 이벤트가 적용되지 않는다.

const list = document.querySelector('#list');

for (let item of list.children) {
  item.addEventListener('click', function(e) {
    e.target.classList.toggle('done');
  })
}

const li = document.createElement('li');
li.classList.add('item');
li.textContent = '일기 쓰기';
list.append(li);

이 문제는 이벤트 버블링을 활용하면, 생각보다 쉽게 해결이 가능하다.

부모 요소는 자식 요소에서 발생한 이벤트를 감지할 수 있고, event 객체의 target 프로퍼티는 항상 이벤트 발생 위치를 담고 있기 때문에,

앞선 예시처럼, item 각각에 대한 이벤트를 등록하는 것이 아니라, 아래 예시처럼, 부모 요소에만 이벤트 핸들러를 등록해도, 모든 자식 요소의 이벤트를 다룰 수 있다.

이를 활용하면, 새로 추가된 자식 요소에서도 동일하게 이벤트를 다룰 수 있다.

const list = document.querySelector('#list');

list.addEventListener('click', function(e) {
  e.target.classList.toggle('done');
})

const li = document.createElement('li');
li.classList.add('item');
li.textContent = '일기 쓰기';
list.append(li);

다만, 이 경우 문제가 하나 발생하는데, 자식 요소 뿐 아니라 부모 요소에서도 이벤트가 동작한다는 것이다.

때문에, 이벤트 위임을 할 때는, 명확하게 원하는 요소에서 의도한 동작이 일어나도록 따로 처리를 해주어야 한다.

아래 예시처럼, if 문을 활용하여, target의 class에 item이 있는 지에 대한 확인을 거치는 과정을 추가하면, 부모 요소에서는 이벤트가 동작하지 않도록 제한할 수 있다.

const list = document.querySelector('#list');

list.addEventListener('click', function(e) {
  // if (e.target.tagName === 'LI')
  if (e.target.classList.contains('item')) {
    e.target.classList.toggle('done');
  }
}
profile
프론트엔드 개발자가 되기 위해 노력 중인 새싹🌱 입니다.

0개의 댓글