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

NSH·2022년 6월 20일
0

이벤트(event)

이벤트는 웹 어플리케이션에서 사용자의 입력을 받기 위해서 필요한 기능이다.

let handleAddItem = event => console.log(event);
let button = document.querySelector('button');
button.addEventListener('click', handleAddItem);
<button>Add Item</button>

위 예제 코드는 Add Item버튼을 클릭하면 handleAddItem핸들러가 실행되고 event를 출력해 이벤트에 대한 정보를 확인할 수 있다.

Web APIaddEventListener는 화면에 동적인 기능을 추가하기 위해서 자연스럽게 접하게 되는 기능이다. 브라우저가 어떻게 이벤트를 감지하는지 두가지 방식에 대해서 자세히 알아보자.

이벤트 버블링(Event Bubbling)

상위 화면 요소
HTML은 트리 구조를 가지고 있으며 트리 구조상 한 단계 위에 있는 요소를 상위 화면 요소라고 부른다.

이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 상위 화면 요소들로 전달되어 가는 특성이다.

위에 첨부한 사진은 아래의 예시를 도식화한 그림이다.

let divs = document.querySelectorAll('div');
let eventLog = event => console.log(event.currentTarget.className);

divs.forEach(div => {
	div.addEventListener('click', eventLog);
})
<body>
  <div class="one">
    <div class="two">
      <div class="three">
      </div>
    </div>
  </div>
</body> 

<div class="three"/> 를 클릭하면 아래와 같은 결과가 나온다.

<div class="three"/> 만 클릭했을 뿐인데 3개의 이벤트가 발생하는 이유는 브라우저가 이벤트를 감지하는 방식이기 때문이다.

브라우저는 이벤트가 발생했을 때 이벤트를 최상위에 있는 화면 요소까지 전파한다. 하위 요소에서 상위 요소로 이벤트가 전파되는 방식을 이벤트 버블링이라고 부른다.

이벤트 캡처링(Event Capturing)

이벤트 캡처링은 이벤트 버블링과 반대 방향으로 이벤트가 전파된다.

이벤트가 발생했을 때 최상위 요소인 body 태그에서 해당 태그를 찾아서 내려간다.

코드로는 이벤트 버블링과 대부분 동일하고 addEventListener() API 옵션 객체에 capture: true를 설정하면 이벤트를 감지하기 위해 이벤트 버블링과 반대 방향으로 탐색을 진행한다.

let divs = document.querySelectorAll('div');
let eventLog = event => console.log(event.currentTarget.className);

divs.forEach(div => {
	div.addEventListener('click', eventLog, {
		capture: true // default: false
	});
})
<body>
  <div class="one">
    <div class="two">
      <div class="three">
      </div>
    </div>
  </div>
</body> 

<div class="three"></div> 를 클릭하면 아래와 같은 결과가 나온다.

이벤트 전파 막기(Event StopPropagation)

event.stopPropagation() Web API를 사용하면 이벤트가 전파되는 것을 막을 수 있다.

이벤트 버블링의 경우에는 클릭한 요소의 이벤트만 발생시키고 상위 요소로 이벤트를 전달하는 것을 막을 수 있다.

이벤트 캡처링의 경우에는 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 이벤트를 전달하지 않는다.

// 이벤트 버블링 예제
let divs = document.querySelectorAll('div');

let logEvent = (event) => {
	event.stopPropagation();
  console.log(event.currentTarget.className);
}

divs.forEach(div => {
	div.addEventListener('click', logEvent);
});

// 이벤트 캡처링 예제
let divs = document.querySelectorAll('div');

let logEvent = (event) => {
	event.stopPropagation();
  console.log(event.currentTarget.className);
}

divs.forEach(div => {
	div.addEventListener('click', logEvent, {
    	capture: true
    });
});

⭐이벤트 위임(Event Delegation)⭐

사실 이벤트 버블링과 캡처링은 이벤트 위임을 위한 선수 지식이다. 자주 사용되는 패턴으로 알아두면 좋다.

이벤트 위임은 하위 요소들에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트를 제어하는 방식이다.

let item1 = document.getElementById('item1');
let item2 = document.getElementById('item2');

item1.addEventListener('click', () => {
  alert('clicked');
});
item2.addEventListener('click', () => {
  alert('clicked');
});
<ul class="itemList">
	<li>
  	<input type="checkbox" id="item1" />
    <label for="item1">이벤트 버블링 학습</label>
  </li>
	<li>
  	<input type="checkbox" id="item2" />
    <label for="item2">이벤트 캡쳐링 학습</label>
  </li>
</ul>

위 예제 코드를 실행하면 문제없이 잘 동작한다.

여기서 할 일이 더 생겨 리스트 아이템을 추가하면 클릭 이벤트가 없어 클릭해도 동작하지 않는다.

// 리스트 아이템을 추가하는 코드
let itemList = document.querySelector('.itemList');
let li = document.createElement('li');
let input = document.createElement('input');
let label = document.createElement('label');
let labelText = document.createTextNode('이벤트 위임 학습');

input.setAttrubute('type', checkbox);
input.setAttribute('id', 'item3');
label.setAttribute('for', 'item3');
label.appendChild(labelText);
li.appendChild(input);
li.appendChild(label);
itemList.appendChild(li);

이유는 새롭게 추가된 아이탬에는 클릭 이벤트 리스너가 등록되지 않았다.

새롭게 아이템이 추가될 때마다 이벤트 리스너를 일일이 달아주면 작업이 매우 번거롭고 모든 아이템에 이벤트 리스너가 등록되면 메모리도 많이 잡아먹을 수 있다.

위에 언급한 문제들은 이벤트 위임으로 해결할 수 있다.

// 기존 이벤트 등록 코드 주석 처리
//let item1 = document.getElementById('item1');
//let item2 = document.getElementById('item2');
//item1.addEventListener('click', () => {
//  alert('clicked');
//});
//item2.addEventListener('click', () => {
//  alert('clicked');
//});

// 상위 요소인 ul 태그에 이벤트 리스너를 등록
let itemList = document.querySelector('.itemList;
itemList.addEventListener('click', event => {
	alert('clicked');
});

상위 요소인 ul 태그에 이벤트 리스너를 등록하고 하위에서 발생한 이벤트 클릭 이벤트를 감지할 수 있기 때문에 아이템이 추가 될 때마다 이벤트를 등록할 필요가 없어진다.

profile
잘 하고 싶다.

0개의 댓글