1. 이벤트의 흐름
계층적 구조에 포함되어 있는 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어난다. 즉, 이벤트가 전파(Event Propagation)되는데 전파 방향에 따라 다음과 같이 2개로 구분합니다.
버블링(Event Bubbling)- 자식 요소에서 발생한 이벤트가 부모 요소로 전파
- cf. 거품이 퐁퐁퐁퐁 일어나듯이 이벤트가 퐁퐁퐁퐁 일어나 ‘버블링(Bubbling)’이라는 이름을 갖게 됨
- 이벤트 발생 요소에서부터 순서대로 최상위 부모 요소까지 퐁퐁퐁퐁 이벤트가 연달아 발생
캡처링(Event Capturing)- 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달
cf. 버블링과 캡처링은 둘 중에 하나만 발생하는 것이 아니라 캡처링부터 시작하여 버블링으로 종료합니다.
즉, 이벤트가 발생했을 때 캡처링과 버블링은 순차적으로 발생합니다.
2. 이벤트 버블링
1<!doctype html>2<html lang="ko">3<head>4<title>이벤트 버블링</title>5<style>6.upper {7background: gold;8width: 70px;9text-align: center;10}11.middle {12background: orange;13width: 50px;14text-align: center;15}16.lower {17background: pink;18width: 30px;19}20</style>21</head>22<body>23<div class="upper" onClick="clicked(event)" id="물">24물25<div class="middle" onClick="clicked(event)" id="고">26고27<div class="lower" onClick="clicked(event)" id="기">기</div>28</div>29</div>30<script>31function clicked(event) {32alert(event.currentTarget.getAttribute('id'))33}34</script>35</body>36</html>
위 코드를 실행하면 다음 그림과 같은 결과가 나옵니다.

여기서 가장 내부에 자리 잡고 있는 기를 클릭하면 어떤 순서대로 이벤트가 실행될까요?
이벤트 버블링이 default로 설정되어 있으므로 기, 고, 물의 순서대로 이벤트가 발생합니다.
그러므로 위 코드라면 ‘기’ -> ‘고’ -> ‘물’의 순서대로 알림창이 뜹니다.
고를 클릭하면 ‘고’ -> ‘물’의 순서로 알림창이 뜹니다.
그렇다면 순서를 ‘물’ -> ‘고’ -> ‘기’으로 만들려면 어떻게 해야 할까요? 바로 이벤트 캡쳐를 사용하면 됩니다.
3. 이벤트 캡처링
이벤트 캡쳐를 사용하려면 addEventListener 내부에 capture 값을 명시적으로 true로 변경해줘야 합니다. 기본값은 false이며, false는 이벤트 버블링, true는 이벤트 캡쳐를 의미합니다.
1<!doctype html>2<html lang="ko">3<head>4<title>이벤트 캡처링</title>5<!-- 생략 -->6</head>7<body>8<!-- 생략 -->9<script>10function clicked(event) {11alert(event.currentTarget.getAttribute('id'))12}13const divs = document.querySelectorAll('div')14divs.forEach(function (div) {15div.addEventListener('click', clicked, {16capture: true,17})18})19</script>20</body>21</html>
capture:true로 설정해주고 다시 기를 클릭하면 이번에는 반대 순서인 위에서 아래로 이벤트가 발생합니다.
이제는 ‘물’ -> ‘고’ -> ‘기’ 의 순서로 알림이 뜹니다.
4. 이벤트 전파 방지 : stopPropagation
그렇다면 ‘이벤트 버블링도 싫고, 이벤트 캡쳐도 싫으니 둘 다 하지마!’라고 명령하려면?
- 이벤트와 함께 실행될 함수 내부에
event.stopPropagation()을 넣어주면 됩니다. - cf. 프로파간다(propaganda)를 닮은 그 단어 ‘propagation’은 ‘번식, 증식, 전파’등의 의미를 갖고 있음
따라서 ‘이벤트의 증식 또는 전파를 멈추겠다’는 의미로 event.stopPropagation()를 사용하면 됩니다.
1<!doctype html>2<html lang="ko">3<head>4<title>이벤트 전파 방지</title>5<!-- 생략 -->6</head>7<body>8<div class="upper" onClick="clicked(event)" id="물">9물10<div class="middle" onClick="clicked(event)" id="고">11고12<div class="lower" onClick="clicked(event)" id="기">기</div>13</div>14</div>1516<script>17function clicked(event) {18event.stopPropagation() // 추가19alert(event.currentTarget.getAttribute('id'))20}21</script>22</body>23</html>
이제 더 이상 ‘물고기’ 또는 ‘기고물’같은 단어를 완성하지 않습니다.
그저 기를 누르면, 기처럼 해당 문자만 띄웁니다.
5. target과 currentTarget 차이
target은 이벤트가 발생한 바로 그 요소를 직접 가리키고,currentTarget은 이벤트 리스너(EventListener)를 가진 요소를 가리킵니다.
1<!doctype html>2<html lang="ko">3<head>4<title>target과 currentTarget 차이</title>5<style>6.upper {7background: gold;8width: 80px;9text-align: center;10}11.lower {12background: pink;13width: 50px;14}15</style>16</head>17<body>18<div class="upper" onClick="clicked(event)" id="div">19<span class="lower" id="span">span</span>20</div>2122<script>23function clicked(event) {24alert(event.target.id + ' clicked')25}26</script>27</body>28</html>
위 코드를 실행하면 다음 그림과 같은 결과가 나옵니다.

노랑은 div, 핑크는 span이며, 핑크가 노란색 위에 앉아있습니다. 그런데 onClick 이벤트는 div에서 설정했지만 노랑을 눌러도, 핑크를 눌러도 모두 이벤트가 발생합니다. div에만 이벤트를 설정했는데, 왜 자식인 span도 이벤트를 상속받는 것일까요? 바로 이벤트 버블링, 이벤트 캡쳐링과 연관이 있습니다.
이벤트 발생에 따른 target은 다음과 같습니다.
- 핑크 부분을 클릭
target: 핑크 (핑크를 눌렀으므로 핑크가 이벤트 발생 시점이 됨)currentTarget: 노랑 (onClick 이벤트는 노랑이 갖고 있음)
- 노란 부분을 클릭
target: 노랑(노랑을 눌렀으므로 노랑이 이벤트 발생 시점이 됨)currentTarget: 노랑(onClick 이벤트는 노랑이 갖고 있음)
따라서 노랑을 누르면 ‘div clicked’, 핑크를 누르면 ‘span clicked’ 알림창을 띄웁니다.
만약 핑크를 눌러도 이벤트를 가진 노랑의 속성에 접근하고 싶다면, currentTarget과 getAttribute를 사용하면 됩니다.
1<!doctype html>2<html lang="ko">3<head>4<title>target과 currentTarget 차이2</title>5<style>6.upper {7background: gold;8width: 80px;9text-align: center;10}11.lower {12background: pink;13width: 50px;14}15</style>16</head>17<body>18<div class="upper" onClick="clicked(event)" id="div">19<span class="lower" id="span"> span </span>20</div>2122<script>23function clicked(event) {24alert(event.currentTarget.getAttribute('id') + ' clicked')25}26</script>27</body>28</html>
위 코드는 핑크, 노랑 둘 다 이벤트를 가진 노랑 속성에만 접근합니다.
target은 누른 바로 그 것currentTarget은 이벤트를 실행하는 바로 그 것으로 이해하면 됩니다.
6. 이벤트리스너 안에서 쓰는 이벤트 함수들
1document.querySelector('.black-bg').addEventListener('click', function (e) {2e.target // 유저가 실제로 누른거3e.currentTarget // 이벤트리스너 달린곳, 여기서는 querySelector('.black-bg'), this와 동일4e.preventDefault() // 이벤트 기본동작 막아줌5e.stopPropagation() // 내 상위요소로 이벤트 버블링 막아줌6})
이벤트리스너의 콜백함수에 파라미터 아무거나 추가하면, 이벤트관련 유용한 함수들을 사용가능합니다. 파라미터 이름은 아무렇게나 작명하면 됩니다. 보통 대충 e라고 함
-
e.target: 실제 클릭한 요소 알려줌 (이벤트 발생한 곳) -
e.currentTarget: 지금 이벤트리스너가 달린 곳 알려줌 (cf. this라고 써도 똑같음)- 검은배경이 나오기 때문에
e.target == e.currentTarget이렇게 써도 됨 - 아니면
e.target == this이렇게 써도 됨
- 검은배경이 나오기 때문에
-
e.preventDefault()실행하면 이벤트 기본 동작을 막아줌 -
e.stopPropagation()실행하면 내 상위요소로의 이벤트 버블링을 중단해줌
여기서 중요한건 e.target인데, 이벤트 버블링이 일어난다고 해도
사용자가 실제로 클릭한 그 요소는 저 문법으로 찾아낼 수 있습니다.