이벤트 위임
이벤트 위임은 캡쳐링과 버블링을 이용한 것으로, 여러 엘리먼트마다 각각 이벤트 핸들러를 할당하지 않고, 공통되는 부모에 이벤트 핸들러를 할당하여 이벤트를 관리하는 방식이다.
1) 여러 개의 자식 엘리먼트 이벤트를 한 번에 관리
: HTML의 읽기전용 속성인 데이터 속성을 사용하여 이벤트 위임을 구현할 수 있다.
데이터 속성
여기서 데이터 속성은 화면에 안 보이게 글이나 추가 정보를 엘리먼트에 담을 수 있는 속성으로 data- 로 시작하는 속성이다.
<article
id="electriccars"
data-columns="3"
data-index-number="12314"
data-parent="cars">
...
</article>
위처럼 html을 작성하면 각각의 데이터 속성을 읽는 방법은 아래와 같다.
(대시는 camelCase로 변환된다.)
var article = document.getElementById('electriccars');
article.dataset.columns // "3"
article.dataset.indexNumber // "12314"
article.dataset.parent // "cars"
속성을 바꾸기 위해서는 article.dataset.columns = 5 와 같이 작성해주면 된다.
데이터 속성은 Html속성이기 때문에 CSS에서도 접근이 가능한데, 위의 예시에서 data-parent를 article에 보여주기 위해서는 아래와 같이 작성하면 된다.
article::before {
content: attr(data-parent);
}
이때 아래와 같이 attribute selector를 사용하여 데이터에 따른 스타일을 변경해 줄 수 있다.
article[data-columns='3'] {
width: 400px;
}
article[data-columns='4'] {
width: 600px;
}
이를 이용하여 서로 다른 역할을 하는 세 개의 버튼의 이벤트를 어떻게 이벤트 위임 방식으로 처리하는지 살펴보자.
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example</title>
</head>
<body>
<div id="Menu">
<button data-action="save">저장하기</button>
<button data-action="reset">초기화 하기</button>
<button data-action="load">불러오기</button>
</div>
<script src="app.js"></script>
</body>
</html>
const $Menu = document.getElementById('Menu')
const ActionFunctions = {
save: () => alert('저장하기'),
reset: () => alert('초기화하기'),
load: () => alert('불러오기'),
}
$Menu.addEventListener('click', e => {
const action = e.target.dataset.action
if (action) {
ActionFunctions[action]()
}
})
2) 동적 엘리먼츠에 대한 이벤트 관리하기
: 동적으로 추가되거나 삭제하는 엘리먼트에 매번 이벤트 리스너를 추가하고 삭제한다면 코드의 효율성을 낮추고 리스터가 제대로 삭제되지 않는 경우 메모리 누수의 가능성도 커진다. 이때 이벤트 위임의 방식을 사용하여 더 나은 코드를 작성할 수 있다.
아래의 예는 itemList에 두가지 항목을 넣고 자바스크립트 코드에서 각각의 input에 click이벤트 리스너를 달아주는 코드이다.
<h1>오늘의 할 일</h1>
<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>
var inputs = document.querySelectorAll('input');
inputs.forEach(function(input) {
input.addEventListener('click', function(event) {
alert('clicked');
});
});
이때 이상태에서 자바스크립트 코드로 아이템을 아래와 같이 추가하게 된다면 문제가 발생한다.
// 새 리스트 아이템을 추가하는 코드
var itemList = document.querySelector('.itemList');
var li = document.createElement('li');
var input = document.createElement('input');
var label = document.createElement('label');
var labelText = document.createTextNode('이벤트 위임 학습');
input.setAttribute('type', 'checkbox');
input.setAttribute('id', 'item3');
label.setAttribute('for', 'item3');
label.appendChild(labelText);
li.appendChild(input);
li.appendChild(label);
itemList.appendChild(li);
새로 추가된 아이템에도 이벤트 리스너가 등록되길 원했지만 이벤트 리스너가 등록되는 시점에는 리스트 아이템이 기존의 2개만 존재하기 때문에 추가된 아이템에는 등록되지 않음을 확인할 수 있다.
이때 각각의 아이템이 추가될때마다 이벤트 리스너를 달아주는 것보다 상위 요소(예시에서는 itemList)에 이벤트 리스너를 추가해줌으로써 하위 요소의 이벤트를 감지하게 하는 방법이 좋다.
var itemList = document.querySelector('.itemList');
itemList.addEventListener('click', function(event) {
alert('clicked');
});
정리
이벤트 위임은 다음과 같은 알고리즘으로 동작한다.
- 컨테이너에 하나의 핸들러를 할당합니다.
- 핸들러의 event.target을 사용해 이벤트가 발생한 요소가 어디인지 알아냅니다.
- 원하는 요소에서 이벤트가 발생했다고 확인되면 이벤트를 핸들링합니다.
- 장점
- 많은 핸들러를 할당하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약됩니다.
- 요소를 추가하거나 제거할 때 해당 요소에 할당된 핸들러를 추가하거나 제거할 필요가 없기 때문에 코드가 짧아집니다.
- innerHTML이나 유사한 기능을 하는 스크립트로 요소 덩어리를 더하거나 뺄 수 있기 때문에 DOM 수정이 쉬워집니다.
- 단점
- 이벤트 위임을 사용하려면 이벤트가 반드시 버블링 되어야 합니다. 하지만 몇몇 이벤트는 버블링 되지 않습니다. 그리고 낮은 레벨에 할당한 핸들러엔 event.stopPropagation()를 쓸 수 없습니다.
- 컨테이너 수준에 할당된 핸들러가 응답할 필요가 있는 이벤트이든 아니든 상관없이 모든 하위 컨테이너에서 발생하는 이벤트에 응답해야 하므로 CPU 작업 부하가 늘어날 수 있습니다. 그런데 이런 부하는 무시할만한 수준이므로 실제로는 잘 고려하지 않습니다.
--- 참고 사이트
- 이벤트 위임
- [JavaScript] 이벤트 버블링, 캡쳐링, 위임
'JavaScript' 카테고리의 다른 글
Object.assign() (0) | 2022.07.20 |
---|---|
스코프와 렉시컬 스코프 (0) | 2022.05.11 |
이벤트 버블링과 캡쳐링 (0) | 2022.05.08 |
이벤트 루프 (Event Loop) (0) | 2022.02.08 |
[리팩터링 2판 : 4장] 테스트 구축하기 (0) | 2022.01.24 |