Event Flow(이벤트의 흐름)

Event Flow(이벤트의 흐름)이란 무엇인가?


Event Flow란, 어떤 요소에서 이벤트가 발생했을 때, 그 이벤트를 전파하여 감지하는 흐름을 말한다.

이벤트의 흐름은 아래와 같이, 버블링과 캡처링 방식으로 이벤트를 감지한다.


Event Flow
출처: Event Flow


Event Delegation(이벤트 위임)


Event Delegation이란, 다수의 자식 요소에 각각 이벤트 핸들러를 바인딩하는 대신에, 하나의 부모 요소에 이벤트 핸들러를 바인딩하는 방법이다.

이벤트 위임을 하게 되면, 개별 자식 요소에 이벤트 핸들러를 바인딩할 필요가 없을 뿐 아니라, DOM 트리에 새로운 자식 요소가 추가되더라도, 이벤트 처리는 이벤트가 위임된 부모 요소에서 하기 때문에 코드도 간단해지고, 실행속도도 좋아지게 된다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// HTML
<ul id="parent">
<li id="content1">HTML</li>
<li id="content2">CSS</li>
<li id="content3">JAVASCRIPT</li>
</ul>


// Javascript
getColor() {
console.log('get color');
}

var con1 = document.querySelector('#content1').addEventListener('click', getColor);
var con2 = document.querySelector('#content2').addEventListener('click', getColor);
var con3 = document.querySelector('#content3').addEventListener('click', getColor);

만약 이벤트 위임을 안한다면, 위 코드처럼 모든 자식 요소에 이벤트 핸들러를 바인딩해줘야 한다. 위 코드처럼 3개는 크게 문제가 안될 수도 있지만, 만약 10개, 100개면 어떨까? 그렇게 되면 자식요소가 너무 많아지기 때문에, 각 자식요소에 이벤트 핸들러를 바인딩하는 것도 힘들고, 코드 가독성도 안좋게 되고, 결국 실행속도도 저하가 된다.

위 코드를 이벤트 위임을 하면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// HTML
<ul id="parent">
<li id="content1">HTML</li>
<li id="content2">CSS</li>
<li id="content3">JAVASCRIPT</li>
</ul>

// Javascript
getColor() {
console.log('get color');
}

var allContents = document.querySelector('#parent').addEventListener('click', getColor);

위와 같이 부모요소 1개에만 이벤트 핸들러를 바인딩하게 되면, 어떤 자식(하위)요소에서 이벤트가 발생하게 되면 그 이벤트를 부모요소가 감지하여 addEventListener의 콜백함수가 실행되게 된다. 이렇게 하위요소에서 이벤트가 발생하여 상위요소로 전달되어 상위요소가 그 이벤트를 감지하는 것을 버블링이라고 한다. 그 반대 방향으로 감지하는 것을 캡처링이라고 한다. 이부분을 잘 이해해야 이벤트 위임을 잘 할 수 있다.


Event Bubbling(이벤트 버블링) & Event Capturing(이벤트 캡처링)


브라우저가 이벤트를 감지하는 방식은 2가지가 있다.

  • Event Bubbling(이벤트 버블링)
  • Event Capturing(이벤트 캡처링)


Event Bubbling(이벤트 버블링)


이벤트 버블링이란, 자식(하위)요소에서 발생한 이벤트가 부모(상위)요소로 전파(이벤트 전파: Event Propagation)되는 것을 말한다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var ulParent = document.querySelector('#parent');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
})

ulParent.addEventListener('click', function() {
console.log('Event for ul');
})

liChild.addEventListener('click', function() {
console.log('Event for li');
})

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
3
Event for li
Event for ul
Event for div

자식요소인 li를 클릭하면, li에서 이벤트가 발생하고, 그 부모요소인 ul에서 이벤트가 발생하고, ul의 부모요소인 div에서 이벤트가 발생한다. 즉, 이벤트가 발생한 곳부터 위로 하나씩 이벤트가 발생하게 된다. 이때, 어떤 요소에 이벤트가 없을 경우(addEventListener가 없을 경우), 그 요소에서는 이벤트가 발생하지 않는다.

예를 들어, 아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
})

liChild.addEventListener('click', function() {
console.log('Event for li');
})

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
Event for li
Event for div

즉, ul요소는 이벤트가 없기 때문에(addEventListener가 없다.), 이벤트가 발생하지 않고, 그 부모요소인 div요소로 넘어가 div요소의 이벤트가 발생하게 된다.

또한, li요소가 아닌, ul을 클릭하게 되면, ul요소에서 이벤트가 발생하고, 그 부모요소인 div요소에서 이벤트가 전달된다. 즉, 이벤트가 발생한 요소부터 시작하여 상위요소로 이벤트가 발생하는 방식을 이벤트 버블링이라고 한다.

그럼 여기서 이벤트 위임을 하는 코드를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// HTML
<div class="parent">
<button id="btn1">button 1</button>
<button id="btn2">button 2</button>
</div>


// Javascript
function getColor(e) {
e.target.style.color = 'red';
}

var parent = document.querySelector('.parent');
parent.addEventListener('click', getColor);

위 코드에서는, 모든 자식요소에 이벤트 핸들러를 바인딩하는 대신에, 부모요소 1개에만 이벤트핸들러를 바인딩했는데, 이것을 이벤트 위임이라고 한다. 따라서, 자식요소가 button1, button2 둘 중 어느 곳을 클릭을 하더라도, 이벤트가 부모요소로 전달되어, 부모요소인 parent 요소에서 이벤트를 감지하고 이벤트를 발생시킨다.


Event Capturing(이벤트 캡처링)


이벤트 캡처링이란, 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것을 말한다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var ulParent = document.querySelector('#parent');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
}, true)

ulParent.addEventListener('click', function() {
console.log('Event for ul');
}, true)

liChild.addEventListener('click', function() {
console.log('Event for li');
}, true)

참고로, 캡처링을 적용하려면, addEventListener 메소드의 세번째 매개변수에 true를 설정해야 한다. 설정하지 않으면 default로 버블링이다.

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
3
Event for div
Event for ul
Event for li

자식요소인 li를 클릭하면, 최상위 부모요소인 div에서 먼저 이벤트가 발생하고, 아래 방향으로 하나씩 이벤트가 발생한다. 즉, 버블링과 반대로, 이벤트가 발생한 곳은 자식요소이나, 이벤트를 감지하는 곳은 최상위 부모요소부터 시작하여 실제로 이벤트가 발생한 요소까지 도달하는데, 이런식으로 이벤트를 감지하는 것을 이벤트 캡처링이라고 한다.

예를 들어, 아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// HTML
<div id="ancestor">
<ul id="parent">
parent
<li id="child">child</li>
</ul>
</div>


// Javascript
var divAncestor = document.querySelector('#ancestor');
var liChild = document.querySelector('#child');

divAncestor.addEventListener('click', function() {
console.log('Event for div');
})

liChild.addEventListener('click', function() {
console.log('Event for li');
})

위 코드에서 ‘child’(li 요소)를 클릭하면, 아래와 같은 결과가 나온다.

1
2
Event for div
Event for li

즉, ul요소는 이벤트가 없기 때문에(addEventListener가 없다.), 이벤트가 발생하지 않고, 그 부모요소인 div요소부터 이벤트가 발생하고, 그 다음 li요소에서 이벤트가 발생하게 된다.

또한, li요소가 아닌, ul을 클릭하게 되면, 최상위 요소인 div요소에서 이벤트가 발생하여, 이벤트가 발생한 ul요소까지 이벤트가 도달하게 된다. 즉, 이벤트가 어디서 발생하는 것과는 상관없이, 최상위 부모요소에서부터 이벤트가 발생하기 시작하여, 실제로 이벤트가 발생한 곳까지 가는 방식을 이벤트 캡처링이라고 한다.