게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌23 - JavaScript - 8년 전 등록 / 4년 전 수정

이벤트 리스너와 콜백(event listener, callback)

안녕하세요. 이번 시간은 이벤트 리스너콜백 차례입니다.

지금까지는 이벤트 리스너를 안 다뤘습니다만 이제 다룰 때가 된 것 같습니다. 이벤트 리스너와 콜백 방식도 자바스크립트에서 많이 사용되는 방식입니다. 클로저나 IIFE처럼요. 그리고 반드시 알아두어야 합니다. 이 부분에서 많은 입문자들이 고통받습니다.

이벤트 리스너

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

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();
};

대표적인 메소드로 preventDefaultstopPropagation, 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의 역할만, 자바스크립트는 자바스크립트의 역할만 충실히 하는 모습입니다. 보기 좋습니다.

다음 시간에는 함수의 메소드에 대해서 알아보겠습니다.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

16개의 댓글이 있습니다.
4년 전
안녕하세요, ZeroCho님,,설명 진짜 잘 읽었습니다,,
설명 듣고 아래와 같이

\u003cbody>
\u003cdiv class="music-box">
\u003ciframe src="https://youtube.com/embed/gqLu2lbln4I?autoplay=1&mute=1&playlist=gqLu2lbln4I">\u003c/iframe>
\u003c/div>

\u003cscript>
document.onclick = function(event){
window.document.write('\u003ciframe width="560" height="315" src="https://youtube.com/embed/sr3O7ArQTYY" frameborder="0" allowfullscreen>\u003c/iframe>')
}
\u003c/script>
까지 잘 만들었는데, 영상을 클릭하면 함수를 실행하고 싶은데,
위의 스크립트에서는 영상이 아닌 그 주변을 클릭해야지만 함수가 실행됩니다,,

유튜브 영상 자체의 속성 때문에 영상 자체를 클릭하면 다른 영상으로 이어질 수 없는걸까요?
4년 전
이벤트가 document에 걸려있어서 그렇습니다. .music-box에다 걸어보세요. document.querySelector('.music-box').onclick
4년 전
바쁘실텐데 답변 주셔서 정말 감사합니다! 시도는 해봤는데 되진 않네요 ㅠㅠ 그래도 설명은 정말 도움이 많이 되었습니다!!
4년 전
안녕하세요, ZeroCho님,,설명 진짜 잘 읽었습니다,,
설명 듣고 아래와 같이

\u003cbody>
\u003cdiv class="music-box">
\u003ciframe src="https://youtube.com/embed/gqLu2lbln4I?autoplay=1&mute=1&playlist=gqLu2lbln4I">\u003c/iframe>
\u003c/div>

\u003cscript>
document.onclick = function(event){
window.document.write('\u003ciframe width="560" height="315" src="https://youtube.com/embed/sr3O7ArQTYY" frameborder="0" allowfullscreen>\u003c/iframe>')
}
\u003c/script>
까지 잘 만들었는데, 영상을 클릭하면 함수를 실행하고 싶은데,
위의 스크립트에서는 영상이 아닌 그 주변을 클릭해야지만 함수가 실행됩니다,,

유튜브 영상 자체의 속성 때문에 영상 자체를 클릭하면 다른 영상으로 이어질 수 없는걸까요?
4년 전
안녕하세요, ZeroCho님,,설명 진짜 잘 읽었습니다,,
설명 듣고 아래와 같이

\u003cbody>
\u003cdiv class="music-box">
\u003ciframe src="https://youtube.com/embed/gqLu2lbln4I?autoplay=1&mute=1&playlist=gqLu2lbln4I">\u003c/iframe>
\u003c/div>

\u003cscript>
document.onclick = function(event){
window.document.write('\u003ciframe width="560" height="315" src="https://youtube.com/embed/sr3O7ArQTYY" frameborder="0" allowfullscreen>\u003c/iframe>')
}
\u003c/script>
까지 잘 만들었는데, 영상을 클릭하면 함수를 실행하고 싶은데,
위의 스크립트에서는 영상이 아닌 그 주변을 클릭해야지만 함수가 실행됩니다,,

유튜브 영상 자체의 속성 때문에 영상 자체를 클릭하면 다른 영상으로 이어질 수 없는걸까요?
4년 전
자바스크립트에서 이벤트 캡쳐링을 막는 방법은 없나요? 검색해보니 안나와서..
버블링 막기 : e.stopPropagation();
기본 동작 막기 : e.preventDefault();
는 있는데 캡쳐링을 막는 방법은 못찾겠네요
4년 전
이벤트는 기본적으로 버블링되는데 캡처링을 쓰신 이유가 있나요? 캡처링을 막지 마시고 캡처링을 풀고 버블링을 막으시면 됩니다.
4년 전
👍 감사합니다.
4년 전
익명 콜백을 쓰는 이유는
자바 스크립트 한줄 한줄이 다 쓰레드성이라고 보심 됩니다.
밑에 님께서 setTimeout 혹은 setInterval 로 값변화를 체크하고 계신데
이렇게 체크하는 이유가 스크립트의 쓰레드 성 때문입니다
콜백을 잘써야 하는 이유는 주로 많이 쓰는 곳이 ajax call 을 하고 나서입니다
ajax 를 실행하고 나서 보시면 알겠지만 ajax 가 모든 실행이 그러니까 리턴값이 오지 않은 상황에서도 바로 다음 라인이 실행되는걸 보실수 있을꺼에요. ajax의 리턴값을 받아서 다음 훅속동작을 하여야 하는데 님 글처럼 setTimeout 이나 setInterval 을 사용하는것은 여러가지 면에서 코스트가 너무 많이 듭니다.
4년 전
제가 왜 setTimeout을 썼는지 전혀 이해를 못하시는 것 같은데요? 비동기 예시를 들어야하는데 실행가능한 가장 간단한 예시가 setTimeout이라서 쓴 겁니다.
4년 전
onclick에 이벤트 걸었다고 내부 eval 을 콜한다는건 금시초문이네요. jquery 에서는 하위버전에서 eval 을 사용한 경우가 있었지만요. 자바스크립트에서 이벤트 실행시에는 보통 window[]() 이런식으로 처리합니다.
5년 전
콜백질문이요.

솔직히 콜백의 필요성에 대해서 잘 모르겠어요.

본문에서, 영어로는 callback인데 call + back 즉, 다시 전화주는 거죠. 이벤트가 실행됐을 때, 사용자에게 다시 알려주는 겁니다. 이렇게 설명하셨잖아요.

여기에서, 콜백이 어떤무언가 오래걸리는걸 어디론가 보내고, 블록킹이 안되게, 다음 행동을 수행하고, 콜백은 그 오래걸린게 완료될때 작동시키는라고 이해를 했습니다.

그런데 막상 코드를 이렇게 쳐보니, 콜백을 안써도 같은 결과가 나오더라구요.

var cbExample = function(number) {

setTimeout(()=>{
var sum = 0;
for (var i = number; i > 0; i--) {
sum += i;
}

console.log(sum);
},1000)
};

cbExample(10); // 55

console.log('first');

이 문장에서 제가 이해 하기로는,

1. 콜스택에 main 함수가 실행
2. 콜스택에 cbExample(10) 이 실행
3. 콜스택에 setTimeout들어가나, 비동기로 webAPI를 거쳐서, 콜백큐에서 setTimeout이 동작을 하게 나둔다.
4. setTimeout이 동작하는 동안, 콜 스택에 console.log('first')가 작동후 프린트가 된다.
5. 이벤트 루프는 setTimeout의 동작이 끝났는지, 콜스택이 완전히 비웠는지 서로 번갈아 가며 확인후,
조건이 충족되면, 콜백큐에 있는 setTimeout을 콜스택에 옮기게 해서, console.log(sum)을 프린트 하게한다.

로 이해를 하고 있는데요.

질문1,
근데 콜백이 들어간거와 안들어간거의 차이를 모르겠네요.. 여기서요... ??

질문2,
콜백이 들어가면, 콜백도 순서 3번의 setTimeout과 동시에 콜백큐에 들어가는건가요?
5년 전
1. 네 콜백도 비동기 콜백이 있고 동기 콜백이 있습니다. 동기 콜백인 경우에는 코드에서 차이점을 못 느끼실 겁니다.
2. 콜백은 3초 뒤에 webAPI에서 콜백큐로 넘어갑니다.
5년 전
안녕하세요 ZeroCho님.
궁금한 것이 있습니다.
\u003ca href="특정주소">...\u003c/a>라는 DOM 구조가 있을 때, querySelector('a[href]')로 선택해서 onclick 이벤트에 ajax콜을 하는 함수를 바인드해줬는데요.
ctrl+클릭 같이 새 탭으로 해당 링크를 클릭할 때만, ajax콜이 실행됩니다.
일단은 preventDefault()사용해서 멈추고 success와 error에서 location.href에 "특정주소"를 넣어주는 식으로 해서 해결했는데, 혹시 그냥 클릭했을 때 ajax콜이 실행되지 않는 이유를 아시나요?
5년 전
포스팅 잘봤습니다.
콜백 설명 예시로 cbExample에서 first 와 55 외에 undefined가 그 사이에 프린트되던데 저 undeifined는 어디서 오는 값인가요?
5년 전
console.log의 return값이 undefined입니다.
5년 전
안녕하세요. 포스팅 잘 읽고있습니다.
callback을 설명하면서 예를 들어서 사용한 코드에서 질문이 있습니다.
작성하신 코드는 아래와 같이 설정해도 first가 출력 된 후 10까지의 합이 출력이 되는데요.

var cbExample = function (number) {
setTimeout(function () {
var sum = 0;
for (var i = number; i > 0; i--) {
sum += i;
}
printer(sum);

}, 0)

}

var printer = function (result) {
console.log(result)
}

cbExample(10);
console.log('first')

제가 작성한 코드도, 1~10까지의 합을 계산한 후에 printer함수로 결과값을 알려주지 않나요?
굳이 cbExample을 function(number,cb)로 설정하여 함수를 인자로 받을 필요가 있는지 여쭤보고 싶습니다. 감사합니다!
5년 전
네 저건 그냥 예제일 뿐입니다. 그냥 콜백이 저런 거라는 것을 보여주는 겁니다.
6년 전
html에 이벤트 리스너를 연결하기보다는 코드를 분리하는게 낫다고 하셔서 분리하려는데요

함수와 인자가 한번에 onclick으로 있는 경우엔 어떡하나요??

\u003cdiv id="firstoval" class="oval" onclick="dooduji('firstoval')">\u003c/div>

처럼요
6년 전
document.getElementById('firstoval').onclick = function() {
dooduji('firstoval');
};
6년 전
감사합니다!! 해결했어용

그런데 알려주신거는 다른 함수에 넣어서 그 함수가 실행될떄 onclick에 잘 들어갔는데.. 함수에 안넣고 그냥 이벤트 추가 구문 쓰면 안되더라구요.

그래서 밑에 나왔는거 대로 다 해봤는데 안되서.. 이건 어떻게 해야 할까요?

시작부터 onclick에 startButton();함수와 starChange(); 함수가 있어야 하는데 그냥 HTML에 넣어야 할까요?

window.onload = function () {
remover.addEventListener('click', startButton);
remover.addEventListener('click', startChange);
};


window.onload = function () {
remover.onclick = function () {
startButton();
};
remover.onclick = function () {
startChange();
};
};


const prepare = () => {
remover.onclick = function () {
startButton();
};
remover.onclick = function () {
startChange();
};
};

prepare();


6년 전
저거 window.onload같은 것은 돼야 합니다. 안 되면 코드에 문제가 있는 거에요. 근데 remover같은 것들도 window.onload 안에서 선언하세요.
6년 전
remover을 전역으로 선언한거인데.. 지금 또 뭐 바꾸다 보니까 안건데 아예 let remover = document.getElementById('start');을 전역으로 선언해서 함수들에서 쓰려니까 안되더라구요.

이게 왜 안되는지를 모르겠네요 이벤트 리스너 추가가 안되는것도 이것 때문인것 같은데..

document는 전역 변수에서 사용하면 안되나요?
6년 전
혹시 스크립트가 HTML 태그보다 위에 있나요?
6년 전
\u003chead>
\u003cmeta charset="utf-8" />
\u003ctitle>두더지 게임\u003c/title>
\u003cscript src="./dooduji.js">\u003c/script>
\u003clink rel="stylesheet" href="./style.css">
\u003c/head>

이렇게 js랑 html이랑 나눠서 하고 있습니당
6년 전
아 그리고 let remover; 만 전역으로 선언하고 함수 안에서 따로 remover = document.getElementbyId('start'); 해주면 잘 됩니당.. 왜 이런걸까요...

let remover = document.getElementbyId('start');를 전역으로 선언할때만 안되네요
6년 전
스크립트가 HTML 태그보다 위에 있으면 당연히 에러납니다. 스크립트 로딩할 때 HTML이 없으니까요. 그래서 스크립트는 보통 바디 제일 아래에 두는 거고요.
6년 전
아 스크립트는 바디 제일 아래에 두는군요.. head에 두는줄 알았습니다. 감사합니다!!
6년 전
안녕하세요. 위 내용을 따라서 thired 태그에 click 과 mouseover 이벤트를 걸고 event.stopImmediatePropagation(); 를 태스트 한 결과, mouseover 이벤트가 발생 하더라구요. third 태그에 click event 를 두개 셋팅 한경우에 event.stopImmediatePropagation(); 를 설정해둔 click 이벤트만 동작하더라구요. event.stopImmediatePropagation() 개념을 살짝 다르게 설명 하신거같아용!!
6년 전
수정했습니다 감사합니다
6년 전
정말 잘보고갑니다. 많은 도움이 됐습니다.
7년 전
중요한 건 아니지만... 오타가 보여서 알려드려요.
꼭 이벤트가 아니더라고 콜백을 활용할 수 있습니다.
-> 꼭 이벤트가 아니더라'도' 콜백을 활용할 수 있습니다.

덕분에 잘 배우고 있습니다. 감사합니다 :-)
7년 전
감사합니다!
7년 전
잘 배워갑니다~