자바스크립트 이벤트 — 클릭에 반응하는 화면 만들기와 버블링

이벤트는 사용자가 일으키는 사건입니다. 클릭, 글자 입력, 폼 전송, 키 누름이 모두 이벤트입니다. 이 글은 “버튼이 반응하지 않는” 증상 다섯 가지를 진단하며 이벤트 문법과 버블링·위임을 익힙니다.

증상 1: 버튼을 눌러도 아무 일이 없습니다

정상 동작하는 기본형입니다. addEventListener의 인수는 사건의 이름과 그때 실행할 함수 두 개입니다.

<button id="saveBtn">글 저장</button>
<p id="status"></p>

<script>
  const saveBtn = document.querySelector("#saveBtn");
  const status = document.querySelector("#status");

  saveBtn.addEventListener("click", function () {
    status.textContent = "저장했습니다.";
  });
</script>

반응이 없다면 원인은 대개 셋입니다. 선택자를 잘못 써서 saveBtnnull이거나, 스크립트가 버튼보다 먼저 실행되었거나, 이벤트 이름의 철자가 틀린 경우입니다.

증상 2: 페이지를 열자마자 한 번 실행되고 끝납니다

가장 많이 나오는 실수입니다. 두 번째 인수에 함수 이름 대신 함수를 호출한 결과를 넣은 것입니다.

function saveNote() { status.textContent = "저장했습니다."; }

saveBtn.addEventListener("click", saveNote());   // 틀림 — 즉시 실행됩니다
saveBtn.addEventListener("click", saveNote);     // 맞음 — 함수 자체

saveNote()는 그 자리에서 실행되고 결과값 undefined를 넘깁니다. 그래서 페이지가 열리는 순간 문구가 바뀌고 정작 클릭에는 반응하지 않습니다. 괄호를 빼고 이름만 넘겨야 합니다. 인수를 전달하려면 화살표 함수로 감쌉니다.

saveBtn.addEventListener("click", () => saveNote("임시저장"));

함수를 값처럼 넘기는 구조가 낯설다면 자바스크립트 함수 편의 콜백 부분을 다시 보십시오.

증상 3: 어떤 요소가 눌렸는지 알 수 없습니다

이벤트가 발생하면 브라우저는 사건 정보를 담은 객체를 콜백의 첫 인수로 넣어 줍니다. 보통 e로 받습니다.

document.querySelector("#noteList").addEventListener("click", function (e) {
  console.log(e.target);          // 실제로 클릭된 요소
  console.log(e.currentTarget);   // 이벤트를 연결한 요소
});

e.target은 사용자가 진짜로 누른 요소, e.currentTargetaddEventListener를 붙여 둔 요소입니다. 목록 전체에 이벤트를 걸었을 때 e.target이 결정적입니다.

증상 4: 폼을 제출하면 화면이 새로 고쳐집니다

자주 쓰는 이벤트부터 봅니다.

이벤트 이름 발생 시점
click 버튼이나 목록을 눌렀을 때
input 입력창의 글자가 바뀔 때마다
submit 폼이 제출될 때
keydown 키를 누른 순간

form은 제출되면 페이지를 다시 불러옵니다. 자바스크립트로 처리하려면 그 기본 동작을 막아야 합니다.

const titleInput = document.querySelector("#titleInput");

document.querySelector("#noteForm").addEventListener("submit", function (e) {
  e.preventDefault();               // 새로 고침 방지

  if (titleInput.value.trim() === "") {
    status.textContent = "제목을 입력해 주십시오.";
    return;
  }
  status.textContent = `"${titleInput.value}" 글을 등록했습니다.`;
});

titleInput.addEventListener("input", (e) => {
  console.log("글자 수: " + e.target.value.length);
});

document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") status.textContent = "";
});

e.preventDefault()는 브라우저가 원래 하려던 일을 취소하며 링크 이동을 막을 때도 씁니다. e.key에는 누른 키 이름이 들어 있습니다.

증상 5: 나중에 추가한 항목은 클릭이 안 됩니다

이벤트는 발생한 요소에서 멈추지 않고 부모를 따라 위로 전달됩니다. 이것을 버블링이라고 합니다. li를 누르면 liulbody 순으로 올라갑니다. 이 성질을 이용해 부모에 한 번만 이벤트를 거는 방식이 이벤트 위임입니다.

const noteList = document.querySelector("#noteList");

noteList.addEventListener("click", function (e) {
  if (!e.target.classList.contains("note")) return;   // 항목이 아니면 무시
  e.target.classList.toggle("done");
});

// 나중에 추가된 항목도 그대로 동작합니다
const newNote = document.createElement("li");
newNote.textContent = "이벤트 정리";
newNote.classList.add("note");
noteList.appendChild(newNote);

li에 직접 이벤트를 걸었다면 나중에 만든 항목은 반응하지 않습니다. 요소를 붙이는 과정은 자바스크립트 DOM 편에서 다뤘고, 제이쿼리 입문on()도 원리는 같습니다.

핵심 정리

  • addEventListener — 이벤트 이름과 함수를 연결하며 괄호 없이 넘깁니다.
  • 이벤트 객체 — 콜백의 첫 인수이며 e.target이 실제 눌린 요소입니다.
  • 자주 쓰는 이벤트click, input, submit, keydown이 대부분입니다.
  • preventDefault — 폼 제출이나 링크 이동 같은 기본 동작을 취소합니다.
  • 버블링과 위임 — 부모에 한 번만 걸어 자식 전체를 처리합니다.