본문으로 건너뛰기

이벤트와 리스너

자바스크립트 목차웹 페이지 움직이기레슨 69 / 76

목차

웹 페이지가 사용자의 클릭이나 키보드 입력에 반응하려면, 요소에 **"이 일이 일어나면 이 함수를 불러 달라"**고 미리 맡겨 두어야 합니다. 브라우저에서 일어나는 사건을 **이벤트(event)**라 부르고, 이벤트를 기다렸다가 실행되는 콜백 함수를 **이벤트 리스너(event listener)**라고 부릅니다.

5단계 중 1단계. btn.addEventListener('click', handle)은 괄호 없이 함수 자체를 맡깁니다. 아직 누르기 전이라 화면은 '클릭 0번'입니다.

1클릭 리스너 등록하기: addEventListener

요소에 리스너를 등록할 때는 요소.addEventListener에 '이벤트이름'과 콜백함수를 넘깁니다.

<p id="count">0</p>
<button id="plus" type="button">
  +1 올리기
</button>
결과 화면

단추(#plus)를 누를 때마다 숫자(count)를 1씩 올려 화면(#count)에 보여 주는 코드입니다.

'use strict';

let count = 0;
const label = document
  .querySelector('#count');
const btn = document
  .querySelector('#plus');

이어서 단추에 클릭 리스너를 등록합니다.

btn.addEventListener(
  'click',
  () => {
    // 1. 상태 바꾸기
    count = count + 1;
    // 2. 화면 고치기
    label.textContent = count;
  },
);
실행 결과
처음 화면: 0
단추를 한 번 누르면: 1
단추를 또 한 번 누르면: 2
  • addEventListener('click', ...): 지금 당장 화살표 함수를 실행하는 것이 아니라, 나중에 'click'이 일어났을 때 **할 일 줄(task queue)**에 올려 실행해 달라고 브라우저에 맡겨 둡니다(규칙 8).
  • 콜백 안에서는 1. 상태 바꾸기(count = count + 1;)와 2. 화면 고치기(label.textContent = count;) 두 단계를 나란히 적습니다. count만 올리고 label.textContent를 갱신하지 않으면 단추를 아무리 눌러도 화면은 0에 멈춰 있습니다.

2함수를 넘길 때 ()를 붙이지 않는다

이름 붙은 함수 handle을 만들어 addEventListener의 두 번째 인자로 넘길 때는 괄호 없이 함수 이름 handle만 적어야 합니다.

만약 'click' 뒤에 handle()처럼 ()를 붙이면 무슨 일이 일어나는지 Node에서 똑같이 확인해 봅니다.

function fakeAdd(type, fn) {
  console.log('등록된 값:', fn);
}
function handle() {
  console.log('실행됨!');
}

fakeAdd('click', handle());
실행 결과
실행됨!
등록된 값: undefined

handle()처럼 괄호를 붙이면 리스너를 등록하는 그 줄에서 함수가 딱 한 번 바로 실행되어 버리고, 정작 addEventListener에는 handle()의 반환값인 undefined가 넘어갑니다. 그래서 정작 사용자가 단추를 누를 때는 아무 일도 일어나지 않습니다.

올바르게 함수를 맡기려면 괄호를 뺀 fakeAdd('click', handle)⁠처럼 적어야 합니다.

function fakeAdd(type, fn) {
  console.log(
    '등록된 종류:',
    typeof fn,
  );
}
function handle() {
  console.log('실행됨!');
}

fakeAdd('click', handle);
실행 결과
등록된 종류: function

3리스너 안에서 오류가 나도 페이지는 살아 있다

2레슨 표에서 봤듯이, 브라우저에서는 클릭 리스너가 돌다가 오류가 나더라도 그 한 번의 클릭 실행만 멈추고 웹 페이지는 죽지 않습니다.

'use strict';

let step = 0;
btn.addEventListener(
  'click',
  () => {
    step = step + 1;
    if (step === 1) {
      throw new Error('첫 오류');
    }
    label.textContent = step;
  },
);
실행 결과
첫 클릭: 콘솔에 Error: 첫 오류
둘째 클릭: 화면이 2로 바뀜

첫 번째 클릭에서 오류가 나도 리스너 등록 자체는 사라지지 않으므로, 두 번째 클릭을 하면 콜백이 다시 할 일 줄에 올라가 step이 2가 되고 화면이 바뀝니다.

예제

결과를 먼저 생각한 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.

다음 코드를 처음 불러왔을 때(아직 단추를 한 번도 누르기 전)와, 단추를 한 번 눌렀을 때 콘솔에 무엇이 찍힐까요?

'use strict';

function greet() {
  console.log('반갑습니다');
}

console.log('준비 시작');
btn.addEventListener(
  'click',
  greet,
);
console.log('준비 끝');
결과 보기
실행 결과
[처음 불러왔을 때]
준비 시작
준비 끝

[단추를 한 번 눌렀을 때]
반갑습니다
  • addEventListener는 greet 함수를 등록만 하고 바로 다음 줄로 넘어가므로 처음에는 '준비 시작'과 '준비 끝'만 찍힙니다.
  • 사용자가 단추를 누르면 그때 할 일 줄에서 greet가 실행되어 '반갑습니다'가 찍힙니다.

자주 틀리는 곳

  1. addEventListener에 handle()처럼 괄호를 붙여 넘긴다: 괄호를 붙이면 페이지가 열릴 때 handle이 한 번 실행되고 끝납니다. 인자 없이 넘길 때는 handle만 적고, 인자를 넘겨야 할 때는 () => handle(1)처럼 화살표 함수로 감쌉니다.
  2. 콜백 안에서 변수만 바꾸고 화면 요소를 안 고친다: count += 1;만 적으면 메모리 속 숫자만 올라갑니다. 바뀐 값을 label.textContent = count;⁠로 화면 요소에 다시 대입해야 눈에 보입니다.
  3. 클릭할 때마다 addEventListener 안에서 또 addEventListener를 부른다: 리스너 등록은 처음에 한 번만 해 두면 클릭할 때마다 계속 불립니다. 리스너 안에서 같은 리스너를 중첩해서 또 등록하면 한 번 클릭에 함수가 두 번, 네 번씩 중복 실행됩니다.

확인 문제

  1. addEventListener의 둘째 인자로 onSave를 넘길 때와 onSave()를 넘길 때의 차이를 설명하세요.
정답

onSave는 함수 자체를 넘겨 단추를 클릭할 때마다 실행되게 하지만, onSave()는 등록하는 줄에서 함수를 즉시 한 번 실행해 버리고 그 반환값(undefined)을 넘기므로 클릭해도 실행되지 않습니다.

  1. 클릭할 때마다 removeItem(3)처럼 인자 3을 넣어 부르고 싶습니다. addEventListener의 두 번째 인자 자리를 어떻게 적어야 하나요?
정답

() => removeItem(3)처럼 화살표 함수로 감싸서 넘깁니다.

  1. 브라우저의 클릭 리스너 첫 번째 실행 도중 TypeError가 났습니다. 사용자가 그 단추를 다시 한 번 누르면 리스너가 실행되나요, 아니면 페이지가 완전히 멈춰 있나요?
정답

다시 실행됩니다. 브라우저에서는 오류가 난 그 한 번의 리스너 실행만 멈추고 페이지와 등록된 리스너는 그대로 살아 있습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로