❗ 해당 문서는 지속적으로 업데이트 될 예정입니다.
❗ 잘못된 정보나 오류가 있으면 댓글로 편하게 남겨주세요!🥰
우리는 이 글을 보기 위해 브라우저를 열고, 갑자기 노래가 듣고싶어져서 브라우저 창의 크기를 반으로 줄인다음, 옆에다가 유튜브로 뮤비를 틀어놓고, 다시 아까 보던 브라우저로 돌아와서 마우스로 글을 클릭하고, 스크롤을 내리며 이 블로그 글잘쓰네 맛집이네라고 생각하며 다 보면 하트를 클릭하거나 댓글 작성하기 버튼을 클릭하여 키보드로 하고싶은 말을 입력한다.
이 모든 것은 이벤트이다.
아, 이벤트는 우리(사용자)가 일으키는 거라고 생각하면 돼?
놉. 관점에 따라 이벤트를 일으키는 주체는 브라우저가 될 수 있다. 사용자가 클릭하고, 입력한 것 말고도 브라우저의 창의 크기가 줄어들거나, 페이지가 로딩이 끝났을 때도 특정한 이벤트가 일어났다고 볼 수 있다. 이벤트를 일으키는 주체는 반드시 사용자일 필요는 없다.
이벤트는 참 다양하고 많구나!!!!
맞다. 약 200여가지의 이벤트가 있고, 우리는 그것을 다 외울 수 없다. 우리가 필수적으로 사용하는 이벤트들만 알면된다.
특히, 우리가 어떤 요소를 클릭을 하면 어떤 페이지로 넘어가게 하거나, 입력을 했을 때 어떤 기능을 하고 싶을 때가 많다. 그렇기 때문에 우리가 이벤트를 이해하고 브라우저는 이벤트를 어떻게 이해하는지를 이해해봐야한다.
나는 그냥 클릭하면 색이 바뀌는 동작을 만들어보고 싶은 것 뿐인데 왤캐 복잡하냐
맞다. 이벤트의 동작 방식은 복잡하다. 하지만 어쩌겠나. 이벤트를 사용하려면 이벤트에 대해 잘 이해하고 있는 것이 필수적인걸!
우리가 만일 이벤트를 사용해 어떤 기능을 하고싶다면, "이 이벤트가 발생했을 때 어떤 기능을 해줘~" 라고 브라우저에게 알려줘야 한다. 이벤트가 발생되었을 때 호출될 함수, 그것이 바로 이벤트 핸들러인데 이 이벤트 핸들러는 일반 함수와 다르게 우리가 직접 호출해줄 수 없다. 이벤트가 일어난 특정 시점에 함수를 호출하고 싶은 것이기 때문에 브라우저한테, '이벤트가 일어나면 이 함수를 실행해줘~'하고 건네주는 것이다.
이벤트 핸들러는 addEventListner라는 메서드를 이용해 등록해준다.
이벤트를 발생시킬 애.addEventListener("어떤 동작",이벤트 핸들러, 캡처사용여부(false가 기본값, 생략 가능))
만약 버튼을 클릭할 때 배경색이 바뀌게 하고싶다면
버튼.addEventListener("click",배경색 바꿔주는 함수)
이와같이 써주면 된다. 여기서 생략된 세번째 인자가 캡처 사용여부라고 나와있는데 이건 뭐냐면, 일단 캡처링과 버블링 단계를 이해해야 이해할 수 있는 개념이다. 간단히 말하자면, 이벤트가 실행되는 단계는 캡처링 단계, 버블링 단계가 있는데 양자택일이다. 어떤 이벤트 핸들러가 캡처링 단계에서 실행되지 않는다면 버블링 단계에 실행된다. 지금은 캡처링 뭐시기 단계에서 실행해 줄거냐? 라고 어떤 조건을 묻는 인자라고만 이해해주자.
이 인자는 생략이 가능한데, 기본값이 false이다. 즉 캡처 뭐시기 그때 실행하지 마쇼가 기본값이다.
우선 이벤트 핸들러는 잠시 치워두고, 캡처링과 버블링 단계를 이해하기 위해 우리는 우선 브라우저가 바보라는 것을 알아둘 필요가 있다. 브라우저 내에서 수많은 이벤트들이 발생하면 우선 브라우저는 해당 이벤트가 일어난 대상을 찾는다.
브라우저는 이벤트가 일어난 대상을 찾기 위해 늘 주시하고 있다. 그리고 이벤트가 발생하면 해당 이벤트가 발생한 좌표에 어떤 요소가 있는지를 확인하기 위해 렌더링 과정 중에 페인트 기록을 열심히 찾아본다.
중요 : 브라우저는 이벤트가 발생하면 이벤트가 발생한 해당 좌표의 요소를 알아낸다!
페인트 기록을 통해 좌표를 알아낸 브라우저는 막상 우리의 의도는 읽지 못한다. 무슨 이야기냐면,
우리는 상식적으로 저 div를 누르면 div에서 이벤트가 실행되었다는 것을 바로 알지만, 브라우저는 바로 알지 못한다. 브라우저의 요소들이 모두 부모 요소들로 감싸져있기 때문이다.
브라우저는 사용자가 div 쪽 좌표를 눌렀을 때 감싸져있는 html을 클릭을 한 것인지, body에서 클릭을 한건지, div를 클릭하려고 한 것인지 알 수 없다. 눈치가 없다. 그래서 브라우저는 고민을한다.
브라우저 : 사용자가 클릭한게(이벤트를 발생시킨게) 이 중 대체 어느요소일까? 모르겠다.
일단 이벤트가 있는지 다 물어보고 있다그러면 다 실행시켜 봐야지.
이렇게 브라우저는 아묻따 실행을 해버린다. 물론 이벤트 핸들러 함수가 한개라면 다행이지만, 다른 요소에도 이벤트 핸들러 함수가 등록되어 있을 수 있다. 여러개일 경우, 하나만 이벤트를 해도 아묻따 실행을 해버린다는 것이다. (충격) 하지만 그 실행에도 순서는 있다. 그 순서가 바로 캡처링과 버블링이다.
일단 브라우저는 이벤트 핸들러들을 마구잡이로 순서없이 실행하진 않는다. 우선, 해당 이벤트가 발생한 좌표에서 가장 깊이가 있는 애를 찾아간다.
가장 안쪽에 있는 애를 무조건 쟤를 "타겟"이라고 이름을 붙여주고, 타겟까지 들어간다. 점점점 안쪽으로 들어가면서 마주치는 모든 요소들에게 다음과 같이 묻는다.
이벤트 리스너가 있니?
😦 : 엥 없는데요? > 그래 알겠다 다음~
😁 : 있어요 > 아까 본 세번째 인자인 캡처 사용 여부를 묻는다.
(이벤트 리스너가 있는 경우) 캡처링때 실행될꺼니?
🤨 : ... > 그래 알겠다 다음~
😗 : false > 그래 알겠다 다음~false라고 대답하거나 대답하지 않으면(생략했으면) 다음요소로 넘어가고,
😄 : true > 이벤트 핸들러 실행~!~!
true라고 대답하면 해당 이벤트 핸들러를 실행해 준다.
이렇게 안쪽으로 들어가면서 만나는 요소들 중 "캡처링" 이벤트로 설정되어있는 이벤트는 모조리 실행시키고, 이 단계를 이벤트 캡처링이라고 한다.
타겟 요소, 즉 이벤트가 일어난 요소의 최상위인 window 객체 부터 캡처링 단계의 이벤트 리스너가 등록되어있는지 확인하고 있다면 실행하고 , 없다면 그 다음 자식단계로 이동하여 또 확인하고, 실행하는 과정을 거쳐 최종적으로 타겟 요소까지 이동을 하게 된다.
반대로, 캡처링이 끝난 이후에는 최초의 이벤트가 발생했던 요소에 버블링 이벤트 리스너가 있다면 실행을 하고, 다음으로 부모 요소로 올라가면서 버블링 이벤트가 있는지 확인하고 버블링 이벤트가 있다면 실행을 하는데, 기본적으로 addEventListner의 3번째 인자인 boolean값은 false가 기본이기 때문에 false라고 명시해주지 않고 생략만 해도 버블링 단계에서 일어난다.
이렇게 이벤트가 우리가 이벤트를 일으킨 요소 하나에서만 일어나지 않고, 타고타고 전파가 일어나는 이유는 아까 말했듯 브라우저가 이벤트 발생 의도를 알지 못하기 때문에, '사용자가 클릭하려고 의도한게 뭐지? 이건가? 이건가?' 하면서 중첩된 요소들의 이벤트가 있다면 전부 다 수행해보기 때문이다. 이 캡처링과 버블링은 우리가 의도하지 않은 요소의 이벤트 핸들러가 동작할 수도 있어 굉장히 불편하지만, 또 이걸 통해 모든 요소에 하나하나 이벤트 리스너를 달아줄 필요 없이 하나에만 이벤트를 넣어주고 동작시키는 이벤트 위임이라는 것을 통해 모조리 이벤트를 줄 수 있다는 장점도 있다. 또한 이 전파를 명시적으로 막아주는 기능도 있다. 이것들은 다음 기회에 알아보좌.
이벤트의 캡처링과 버블링은 우리가 상식적으로 생각하는 것과 다르기 때문에, 익숙치 방식이라 어렵다. 여러번 보고 여러번 이해하려고 노력하다보면 브라우저의 이벤트 동작방식도 귀여워보일 때가 있겠지(..?) 이벤트의 전파막기, 이벤트 target, currentTarget의 차이 등 이벤트로 할 이야기는 너무나도 많다. 모든 걸 이해할 순 없어도 이해하고자 노력은 해보는 개발자가 되보자. 아자자잇.
참고자료
https://javascript.info/bubbling-and-capturing
https://kim-solshar.tistory.com/36
캡쳐링 버블링 왜 생기는지 궁금해서 검색해봤는데 좋은글 잘 읽고 갑니다 ^^