FrontEnd/Javascript

이벤트 리스너, 콜백, 이벤트 버블링에 대하여

NandaNanda 2024. 3. 25. 16:12

이벤트 리스너

이벤트 리스너는 말 그대로 해당 이벤트에 대해 대기중인 겁니다. 항상 리스닝 중이죠. 해당 이벤트가 발생했을 때 등록했던 이벤트 리스너가 실행됩니다.

window.onload = function () {
  alert('I\'m loaded');
};

위의 코드를 보신 적이 있을지 잘 모르겠습니다만, 이 코드가 대표적인 이벤트 리스너입니다. window가 load될 때에 function 부분이 실행되는거죠. load됐다는 것을 누가 알려주냐면, 브라우저가 알려줍니다. 비슷한 코드로, 그리고 자주 쓰이는 코드로 onclick이 있습니다. 이벤트 리스너는 항상 on + '이벤트명'으로 명명됩니다.

자주 쓰이는 이벤트의 목록을 알려드리자면, onblur(객체가 focus를 잃었을 때), onchange(객체의 내용이 바뀌고 focus를 잃었을 때), onclick(객체를 클릭했을 때), ondblclick(더블클릭할 때), onerror(에러가 발생했을 때), onfocus(객체에 focus가 되었을 때), onkeydown(키를 눌렀을 때), onkeypress(키를 누르고 있을 때), onkeyup(키를 눌렀다 뗐을 때), onload(문서나 객체가 로딩되었을 때), onmouseover(마우스가 객체 위에 올라왔을 때), onmouseout(마우스가 객체 바깥으로 나갔을 때), onreset(Reset 버튼을 눌렀을 때), onresize(객체의 크기가 바뀌었을 때), onscroll(스크롤바를 조작할 때), onsubmit(폼이 전송될 때) 등이 있습니다. 이외에도 몇 가지 더 있으니 찾아보세요.

document.getElementById('clickMe').onclick = function () {
  alert('I\'m clicked!');
};

window 말고도, 여러 태그에 각각 이벤트를 설정할 수 있습니다. 모든 DOM들이 이벤트 리스너를 등록할 수 있는 속성들을 갖고 있습니다. 상상할 수 있는 간단한 이벤트는 거의 다 있습니다. 마우스클릭, 키보드 입력, 드래그, 마우스올려놓기 등등요.

이벤트를 붙이는 다른 방법으로 addEventListener가 있습니다. 저는 on으로 붙이는 것보다 이 방식을 더 추천합니다. 여러 이벤트를 등록할 수 있고, 특정 이벤트를 제거(removeEventListener)할 수도 있거든요.

function onClick() {
  alert('I\'m clicked!');
}
function onClick2() {
  alert('또다른 이벤트');
}
document.getElementById('clickMe').addEventListener('click', onClick); // 이벤트 연결
document.getElementById('clickMe').addEventListener('click', onClick2); // 또 하나의 이벤트 연결
document.getElementById('clickMe').removeEventListener('click', onClick); // 연결할 이벤트 중 하나 제거

이렇게 click 위치에는 다른 이벤트에서 on만 떼고 이벤트명을 집어넣으면 됩니다.

콜백

위 두 코드에서 function 부분을 부르는 게 콜백이라고 합니다. 영어로는 callback인데 call + back 즉, 다시 전화주는 거죠. 이벤트가 실행됐을 때, 사용자에게 다시 알려주는 겁니다. onload, onclick 같은 속성에 콜백을 등록하면 됩니다. 위처럼 대입할 수도 있고요.

꼭 이벤트가 아니더라도 콜백을 활용할 수 있습니다. 주어진 숫자부터 1까지 더하는 간단한 함수를 만들어 보겠습니다.

var cbExample = function(number, cb) {
  setTimeout(function() {
    var sum = 0;
    for (var i = number; i > 0; i--) {
      sum += i;
    }
    cb(sum);
  }, 0);
};
cbExample(10, function(result) {
  console.log(result);
}); // 55
console.log('first');

위의 예를 잘 보세요. cbExample 함수의 매개변수 cb가 바로 콜백 함수를 받는 부분입니다. cbExample을 호출할 때 두 번째 인자로 function을 넣은 게 보이죠? 그 함수가 콜백함수로 등록되어 계산이 끝난 후 실행됩니다. 콜백만 등록해두면 계산이 끝난 후 알아서 알려주죠. 위의 예시를 콘솔에 쳐보시면 first가 55보다 아래 줄에 있음에도 먼저 실행됩니다.

여기서 주의할 게 setTimeout 부분입니다. setTimeout 함수를 사용해야 비동기적으로 실행됩니다(0초만에 실행하게 설정해놨는데도 비동기적으로 실행됩니다). 이 부분을 빠뜨리면 55가 first보다 먼저 표시됩니다. 콜백의 의미가 없어지는 거죠.

위의 예에서는 워낙 간단한 계산이라 별로 효용을 느끼지 못하지만, 만약 cbExample의 일이 10초가 걸리는 일이라한다면, cbExample이 끝나고 다음 코드를 실행할 때까지 10초나 기다려야합니다. 그러지말고 콜백을 등록해둔 다음에 바로 다음 코드로 넘어가는 겁니다. 알아서 일이 완료된 후에 알려주도록 만들고요. 이렇게 콜백은 언제 끝날 지 모르는 동작에 대해 그 결과를 전달받을 때 유용합니다. 하염없이 기다릴 필요없이 다 됐을 때 알아서 알려주니까 엄청 편하죠. 꼭 기억해두세요!

이벤트 버블링

이벤트 버블링이라고 들어보셨나요? DOM에 연결한 이벤트는 버블링이 일어납니다. 버블링이란 자식의 이벤트가 부모에도 전달되는 것을 말합니다.

<div id="first"><div id="second"><div id="third"></div></div></div>

위와 같은 구조가 있을 때 div#third를 클릭한 경우, 부모와 조상 태그인 second, first 순으로 같은 클릭 이벤트가 발생합니다. 이 현상을 기억해두세요.

이벤트 객체

DOM에 대한 이벤트에 연결한 함수는 이벤트 객체를 매개변수로 사용할 수 있습니다. 이벤트 객체에는 이벤트에 대한 정보들과 이벤트를 조작하는 메소드들이 들어있습니다.

document.onclick = function(event) {
  event.preventDefault();
  event.stopPropagation();
  event.stopImmediatePropagation();
};

대표적인 메소드로 preventDefault와 stopPropagation, stopImmediatePropagation이 있습니다. preventDefault 메소드는 태그의 기본 동작(예를 들면, a 태그는 클릭 시 링크이동, form 태그은 폼 내용 전송)을 하지 않게 막아주는 역할을 합니다. stopPropagation 메소드는 태그를 클릭 시 부모에게 이벤트가 전달(버블링)되지 않도록 합니다.

stopImmediatePropagation은 버블링을 막음과 동시에 같은 이벤트의 다른 리스너도 실행되지 않도록 합니다. 만약 여러 개의 클릭 이벤트를 동시에 연결한 경우, stopPropagation으로 클릭 이벤트를 막아도 다른 클릭 이벤트는 실행됩니다. 하지만 stopImmediatePropagation으로 클릭 이벤트를 막으면 부모에게는 어떠한 이벤트도 버블링되지 않으면서 다른 클릭 이벤트도 실행되지 않습니다. 단, 다른 종류의 이벤트(마우스 오버 등)는 막지 못합니다.

메소드 외에도 이벤트 객체에는 많은 정보들이 들어있습니다. event.target 안에 이벤트가 발생한 태그의 정보가 들어갑니다. 클릭을 했을 때는 event.pageX, event.pageY로 클릭한 좌표를 알 수 있고, 키보드를 친 경우에는 event.key로 어떤 키를 쳤는지 알 수 있습니다. 이벤트 객체를 활용해서 다양한 이벤트를 만들어보세요!

주의사항

가끔 html 자체에 이벤트 리스너를 연결하시는 분이 있습니다.

<button onclick="showResult()">클릭</button>

이 형식은 별로 권장하지는 않습니다. HTML과 자바스크립트는 분리가 원칙입니다. 그래야 나중에 유지보수가 쉽거든요. 그리고 위와 같이 하면 이벤트가 발생할 때 eval과 비슷한 자바스크립트 내부 메소드가 실행되는 데(new Function) 이러한 eval 기능은 자바스크립트에서 피해야하는 것 중 하나입니다.

<button id="clicker">클릭</button>
<script>
  document.getElementById('clicker').onclick = showResult;
</script>

이렇게 코드를 분리합시다. HTML은 HTML의 역할만, 자바스크립트는 자바스크립트의 역할만 충실히 하는 모습입니다. 보기 좋습니다.