Event Order In Browser
브라우저 이벤트
우리는 브라우저 이벤트를 알고 있다.
click, load, mouseover 등 많은 표준 이벤트들부터 사용자 정의 이벤트까지 다양한 형태의 이벤트가 존재하는걸 알고 있다.
그런데 브라우저에서 해당 이벤트가 어떤 순서로 실행되는지는 알고 있을까? 다음 문제에 한번 답해보자.
Q. 어떤 엘리먼트가 자신의 자식과 같은 이벤트 핸들러를 가지고 있다면, 어떤 이벤트 핸들러가 먼저 실행될까?
A. 브라우저에 따라 다르다.
당연하게도 우리가 이벤트를 발생시킨 엘리먼트의 핸들러만 실행된다고 생각했겠지만, 그건 우리의 착각이다.
예시를 통해 확인해보자.
Outer와 Inner를 차례로 클릭해보라. Result Area를 살펴보면 Inner를 클릭할 때 Outer 클릭 이벤트 핸들러가 실행된다. 예상하던 결과인가?
무엇이 있는걸까?
두가지가 존재한다.
- Event Capturing
- Event Bubbling
2000년에 W3C DOM Level 2 Event Model에서 등장한 개념이다. 그럼 각각 알아보자. 간단한 개념이다.
1. Event Capturing (Top-down check)
: 이벤트가 발생하면, Root (Document)부터 이벤트가 발생한 Element까지 이벤트 핸들러를 확인해간다.
2. Event Bubbling (Bottom-up check)
: Capturing과는 정반대로 동작한다. 이벤트가 발생하면, 이벤트가 발생한 Element부터 Root (Document)까지 이벤트 핸들러를 확인해간다.
해당 과정을 통해 우리는 “일단 이벤트가 발생하면, 루트부터 이벤트가 발생한 엘리먼트까지 이벤트 확인 과정을 2번 반복한다”는 사실을 알 수 있다. 생각만해도 시스템 자원이 줄줄 새는 느낌이니 나중에 끄는 방법을 알아보자.
위의 문제를 방금 배운 개념으로 설명해보자. (Inner 클릭)
- Inner를 클릭해 클릭 이벤트가 발생한다.
- Capturing 단계를 시작한다.
- Capturing 단계에서 확인된 핸들러가 없으므로 Capturing 단계를 끝낸다.
- Bubbling 단계를 시작한다.
- 이벤트가 발생한 엘리먼트(Inner)의 이벤트 핸들러를 확인하고 실행한다.
- Inner와 부모로 올라간다 (Outer)
- Outer의 이벤트 핸들러를 확인하고 실행한다.
- Outer의 부모로 올라간다 (Root Document)
- Root의 이벤트 핸들러를 확인하고, 핸들러가 없으니 이벤트 핸들링을 끝낸다.
뭔가 장황한듯 보이지만, 요약하면 버블링 과정에서 Inner의 핸들러 실행 후 Outer의 핸들러까지 실행했다는 내용이다.
그런데 버블링 과정에 이벤트 등록을 안했는데??
아니다. 했다. 바로 addEventListener API를 통해서 말이다.
Event 등록 함수
위에서 언급한 DOM Level 2 Event Model에서 add/removeEventListener 함수를 정의했다. 한번 살펴보자.
addEventListener( type, listener, useCapture ) {
type: DOMString,
listener: EventListener,
useCapture: boolean
}
type과 listener는 우리가 항상 사용해왔다. 그런데 마지막 인수, 우리는 뭔지 잘 모르고 사용했지만 그 마지막 인수가 Capturing과 Bubbling 중 어느 단계에 이벤트를 등록할지 결정하는 인수였다.
우리는 흔히 false를 기본으로 줬기 때문에 이벤트를 Bubbling 단계에 등록해왔던 것이다.
그런데 왜 버블링일까? 이유는 IE 때문이다.
IE에서는 Capturing 단계가 존재하지 않는다. 뭐라고?!
물론 W3C DOM Level 2 Event Model과도 다른 API를 사용한다. 뭐라고?!
IE8 이전 버전이 그렇다는 거다. IE9는 DOM Level 3 Event Model을 지원했고, IE11부터는 IE만의 Event Model API를 제거했다.
IE 에서는 attachEvent / detachEvent 함수를 사용해 이벤트를 등록한다. 그리고 모든 이벤트는 Bubbling 단계에 등록한다. 이게 바로 우리가 대부분의 이벤트들을 알게 모르게 Bubbling 단계에만 등록해왔던 이유다.
도대체 뭐가 문제지?
일단 Capturing과 Bubbling도 알았고, IE와 W3C Model이 서로 다르다는건 알게 되었다. 그런데 이로 인해 발생하는 상황들을 한번 생각해보자.
- Capturing 단계에 등록한 Event들은 Bubbling 단계에 등록한 이벤트들보다 항상 먼저 실행된다. 문제는, 어떤 이벤트들을 Capturing 단계에 등록했는지 모른다는 것이다.
- Cross Browser 문제를 피하려면 Capturing 단계에 이벤트를 등록하지 말아야 한다. (하지만 우리는 답을 찾을 것이다. 언제나 그랬듯이… 바로 IE8 지원을 버릴 수 있는 서비스만 만들겠어!)
특히 2번 문제는 더 많은 복잡함이 숨어있다. 그러니 맘 편하게 Bubbling 과정에만 이벤트를 등록하자.
난 Bubbling도 싫은데
프론트엔드 개발을 하다보면 생각보다 DOM이 복잡해지는 경우가 많다. 특히 포탈처럼 한 페이지에 여러가지 가져다 붙이는 복잡다단한 DOM들이 넘쳐나는 경우나, 복잡한 SPA(Single Page Application)인 경우 처럼 DOM이 둥둥 떠다닌다 싶으면 Bubbling 과정은 부담이 될 수 밖에 없다.
그럴 때 시스템 자원을 아끼기 위해 Bubbling 과정을 끄고 싶다면 다음 방법을 이용해라.
결론
오늘은 간단하게 브라우저가 이벤트 핸들러를 확인하는 순서에 대해 알아보았다. 짧게 적어보았지만 이에 다룬 다른 글들이 많으므로 헷갈리는 부분이 있다면 참고문헌을 참고하기 바란다.