[JavaScript] Event

배창민·2025년 11월 20일
post-thumbnail

자바스크립트 이벤트 핵심 정리

이벤트 핸들러 · 이벤트 객체 · 전파 · 위임 · 폼 이벤트


1. 이벤트 핸들러와 등록 방식

브라우저는 클릭, 키보드 입력, 마우스 이동 같은 동작을 감지해서 이벤트 객체를 만들고, 등록해 둔 이벤트 핸들러(콜백 함수) 를 호출한다.
이 핸들러를 브라우저에 넘겨두는 과정을 이벤트 핸들러 등록이라고 부른다.

1-1. 이벤트 핸들러 어트리뷰트 방식

HTML 태그 안에 직접 onXXX 속성으로 작성하는 방식.

<button onclick="alert('클릭했네?'); console.log('클릭했네?');">
  클릭해보세요
</button>
<button onmouseover="hello('수강생');">마우스를 올려보세요</button>
<script>
  function hello(name) {
    alert(`${name}씨, 마우스 올리지마세요!`);
  }
</script>

특징

  • onclick="함수호출문" 처럼 함수 호출문을 문자열로 넣는다
  • HTML과 JS가 섞여서 코드가 지저분해지기 쉽다
  • 간단한 테스트 용도 외에는 잘 쓰지 않는 편이 낫다

1-2. 이벤트 핸들러 프로퍼티 방식

DOM 객체의 onXXX 프로퍼티에 함수를 직접 할당.

<button id="btn">클릭해보세요</button>
<script>
  const $button = document.querySelector('#btn');

  $button.onclick = function () {
    alert('DOM 프로퍼티 방식으로 이벤트 핸들러 등록 완료!');
  };

  // 이전 핸들러는 덮어써진다
  $button.onclick = () => alert('이벤트 덮어쓰기!');
</script>

특징

  • HTML과 JS를 분리할 수 있다
  • 하나의 이벤트 타입당 하나의 핸들러만 등록 가능 (덮어쓰기)

1-3. addEventListener 방식

가장 많이 쓰는 방식.

<button id="btn">클릭해보세요</button>
<script>
  const $button = document.querySelector('#btn');

  $button.addEventListener('click', function () {
    alert('클릭했네?');
  });

  // 프로퍼티 방식과 함께 사용 가능
  $button.onclick = function () {
    console.log('프로퍼티 방식 핸들러');
  };

  // 같은 이벤트에 여러 개 핸들러 등록 가능
  $button.addEventListener('click', function () {
    console.log('addEventListener 핸들러 1');
  });
  $button.addEventListener('click', function () {
    console.log('addEventListener 핸들러 2');
  });

  // 동일한 함수 참조는 중복 등록되지 않는다
  const handleMouseover = () => console.log('button mouseover');
  $button.addEventListener('mouseover', handleMouseover);
  $button.addEventListener('mouseover', handleMouseover);
</script>

형식

element.addEventListener('이벤트타입', 핸들러함수, useCapture옵션);
  • 세 번째 인수 true 면 캡처링 단계에서, 기본값 false 면 버블링 단계에서 동작 (전파에서 다시)

1-4. 이벤트 핸들러 제거

addEventListener 로 등록한 핸들러만 removeEventListener 로 제거 가능.

<button id="btn">클릭해보세요</button>
<script>
  const $button = document.querySelector('#btn');
  const handleClick = () => alert('클릭했대요~');

  $button.addEventListener('click', handleClick);
  $button.removeEventListener('click', handleClick); // 제거

  // 이런 식의 익명 함수는 나중에 제거 불가능
  $button.addEventListener('mouseover', () => alert('마우스 올렸대요~'));
</script>

조건

  • 이벤트 타입, 핸들러 함수 참조, 캡처링 여부(useCapture)
    addEventListener 때와 완전히 같아야 제거된다

2. 이벤트 객체와 this

2-1. 이벤트 객체

이벤트가 발생하면 브라우저가 이벤트 객체를 만들어서
이벤트 핸들러의 첫 번째 인수로 넘긴다.

<h1 class="message">아무곳이나 클릭해보세요. 클릭 좌표를 알려준다.</h1>
<script>
  const $msg = document.querySelector('.message');

  function showCoords(e) {
    console.log(e); // 이벤트 객체
    $msg.textContent = `clientX : ${e.clientX}, clientY : ${e.clientY}`;
  }

  document.onclick = showCoords;
</script>

이벤트 어트리뷰트 방식을 쓴다면 매개변수 이름은 반드시 event 를 써야 한다.

<div class="area" onclick="showDivCoords(event)">
  안쪽을 클릭해보세요. 좌표를 알려준다.
</div>
<script>
  const $area = document.querySelector('.area');

  function showDivCoords(e) {
    console.log(e);
    $area.textContent = `clientX : ${e.clientX}, clientY : ${e.clientY}`;
  }
</script>

2-2. this 바인딩

1) 어트리뷰트 방식

<button onclick="handleClick1()">클릭1</button>
<button onclick="handleClick2(this)">클릭2</button>
<script>
  function handleClick1() {
    console.log(this); // 전역 객체(window)
  }

  function handleClick2(button) {
    console.log(button); // 클릭한 버튼 요소
  }
</script>
  • 함수 내부 this 는 일반 함수 규칙대로 전역 객체가 된다
  • 대신 this 를 인수로 넘겨서 요소를 직접 받을 수 있다

2) 프로퍼티 / addEventListener 방식

<button id="btn1">버튼1</button>
<button id="btn2">버튼2</button>
<script>
  const $btn1 = document.querySelector('#btn1');
  const $btn2 = document.querySelector('#btn2');

  $btn1.onclick = function (e) {
    console.log(this);              // 이벤트를 바인딩한 요소
    console.log(e.currentTarget);   // 동일
    console.log(this === e.currentTarget); // true
  };

  $btn2.addEventListener('click', function (e) {
    console.log(this);              // 이벤트를 바인딩한 요소
    console.log(e.currentTarget);   // 동일
    console.log(this === e.currentTarget); // true
  });
</script>

핵심

  • 두 방식 모두 일반 함수로 핸들러를 정의하면
    this === e.currentTarget === 이벤트를 바인딩한 요소

3) 화살표 함수 주의

<button id="btn3">버튼3</button>
<button id="btn4">버튼4</button>
<script>
  const $btn3 = document.querySelector('#btn3');
  const $btn4 = document.querySelector('#btn4');

  $btn3.onclick = e => {
    console.log(this);            // 상위 스코프의 this (window 등)
    console.log(e.currentTarget); // 버튼 요소
  };

  $btn4.addEventListener('click', e => {
    console.log(this);            // 상위 스코프의 this
    console.log(e.currentTarget); // 버튼 요소
  });
</script>
  • 화살표 함수는 자신만의 this 를 갖지 않는다
  • 이벤트 핸들러에서 this 를 쓰고 싶다면 일반 함수를 쓰는 게 안전하다

3. 이벤트 전파와 위임

3-1. 이벤트 전파 단계

이벤트가 발생하면 이벤트 객체는 DOM 트리를 따라 이동한다.

  1. 캡처링 단계: 상위 요소 → 타깃 요소 방향
  2. 타깃 단계: 실제 이벤트 타깃에 도달
  3. 버블링 단계: 타깃 → 상위 요소 방향으로 다시 올라감
<ul id="drink">
  <li>커피</li>
  <li>콜라</li>
  <li>우유</li>
</ul>
<script>
  const $drink = document.querySelector('#drink');

  $drink.addEventListener('click', e => {
    console.log(e.eventPhase);    // 2 또는 3
    console.log(e.target);        // 실제 클릭한 요소(li 또는 ul)
    console.log(e.currentTarget); // 항상 ul
  });
</script>
  • 프로퍼티/어트리뷰트 방식의 핸들러는 타깃 + 버블링 단계만 잡는다
  • addEventListener 는 세 번째 인수를 활용하면 캡처링 단계도 잡을 수 있다

3-2. 캡처링 vs 버블링

<ul id="food">
  <li>김치찌개</li>
  <li>된장찌개</li>
  <li>고등어구이</li>
</ul>
<script>
  const $food = document.querySelector('#food');

  // 캡처링 단계
  $food.addEventListener(
    'click',
    e => {
      console.log('캡처링', e.eventPhase, e.target, e.currentTarget);
    },
    true
  );

  // 버블링 단계
  $food.addEventListener('click', e => {
    console.log('버블링', e.eventPhase, e.target, e.currentTarget);
  });
</script>
  • ul 클릭 시: 둘 다 타깃 단계(2)
  • li 클릭 시: 캡처링(1) → 타깃(2) → 버블링(3) 순서로 통과

3-3. 이벤트 위임 (delegation)

비슷한 역할의 여러 요소에 각각 핸들러를 다는 대신,
상위 공통 요소에 단 한 번만 핸들러를 등록하는 패턴.

<ul id="drink">
  <li>커피</li>
  <li>콜라</li>
  <li>우유</li>
</ul>
const $drink = document.querySelector('#drink');

$drink.addEventListener('click', e => {
  // ul 자체 클릭은 무시하고, li 에서만 반응
  if (e.target.matches('li')) highlight(e.target);
});

function highlight(li) {
  li.classList.toggle('highlight');
}

장점

  • li 가 100개여도 핸들러 1개
  • 동적으로 li 가 추가/삭제되어도 별도 등록 필요 없음

4. 기본 동작 제어

4-1. 기본 동작 막기: preventDefault

링크 이동, 체크박스 체크 같은 브라우저 기본 동작을 막고 싶을 때 사용.

<a href="https://www.google.com">클릭해도 이동 안 되는 링크</a>
<input type="checkbox"> 클릭해도 체크 안 되는 체크박스
<script>
  document.querySelector('a').addEventListener('click', e => {
    e.preventDefault();
  });

  document
    .querySelector('input[type=checkbox]')
    .addEventListener('click', e => {
      e.preventDefault();
    });
</script>

4-2. 전파 막기: stopPropagation

이벤트가 상위/하위로 전파되는 것을 막는다.

<ul id="drink">
  <li>커피</li>
  <li>콜라</li>
  <li>우유</li>
</ul>
<script>
  const $drink = document.querySelector('#drink');

  // 이벤트 위임
  $drink.addEventListener('click', e => {
    if (e.target.matches('li')) e.target.style.color = 'red';
  });

  // 첫 번째 li 에 별도 핸들러
  document.querySelector('li').addEventListener('click', e => {
    e.stopPropagation(); // ul 로 전파되지 않음
    e.target.style.color = 'blue';
  });
</script>
  • 첫 번째 li 클릭 시 ul 의 위임 핸들러는 실행되지 않는다

5. 주요 이벤트 타입 정리

5-1. 마우스 이벤트

주요 타입

  • mousedown / mouseup
  • mouseover / mouseout
  • mousemove
  • click / dblclick
  • contextmenu (우클릭 메뉴)
<button>클릭해보세요</button>
<script>
  const $btn = document.querySelector('button');

  // e.button: 0=왼쪽, 1=가운데, 2=오른쪽
  $btn.addEventListener('mousedown', e =>
    console.log(`mousedown   button=${e.button}`)
  );
  $btn.addEventListener('mouseup', e =>
    console.log(`mouseup     button=${e.button}`)
  );
  $btn.addEventListener('mouseover', e =>
    console.log(`mouseover   button=${e.button}`)
  );
  $btn.addEventListener('mouseout', e =>
    console.log(`mouseout    button=${e.button}`)
  );
  $btn.addEventListener('click', e =>
    console.log(`click       button=${e.button}`)
  );
  $btn.addEventListener('dblclick', e =>
    console.log(`dblclick    button=${e.button}`)
  );
  $btn.addEventListener('contextmenu', e => {
    e.preventDefault(); // 우클릭 메뉴 막기
    console.log(`contextmenu button=${e.button}`);
  });
</script>

5-2. 키보드 이벤트

주요 타입

  • keydown: 키를 눌렀을 때
  • keyup: 키에서 손을 뗐을 때

주요 프로퍼티

  • event.key: 실제 문자 값 (예: "a", "A", "Enter")
  • event.code: 키 위치 코드 (예: "KeyA", "Enter")
<input type="text" placeholder="아무 키나 입력">
<script>
  const $input = document.querySelector('input[type="text"]');

  $input.addEventListener('keydown', e => {
    console.log(`keydown key=${e.key}, code=${e.code}`);
  });

  $input.addEventListener('keyup', e => {
    console.log(`keyup   key=${e.key}, code=${e.code}`);
  });
</script>

5-3. 폼과 입력 요소 이벤트

5-3-1. 폼 요소 접근

<form name="memberjoin">
  <label for="username">이름</label>
  <input type="text" name="username" id="username" value="홍길동">

  <br><br>

  <label>성별</label>
  <input type="radio" name="gender" id="male" value="m" checked>
  <label for="male">남자</label>
  <input type="radio" name="gender" id="female" value="f">
  <label for="female">여자</label>

  <br><br>

  <label>나이</label>
  <select id="age" name="age">
    <option value="10">10대 이하</option>
    <option value="20">20대</option>
    <option value="30">30대</option>
    <option value="40">40대</option>
    <option value="50">50대</option>
    <option value="60">60대 이상</option>
  </select>

  <br><br>

  <label>자기소개</label>
  <textarea
    name="introduce"
    id="introduce"
    rows="5"
    cols="30"
    style="resize: none"
  >저를 소개합니다!</textarea>
  <span>0</span>/5000자

  <br><br>

  <input type="submit" value="제출">
</form>
<script>
  console.log(document.forms);            // 모든 form
  const $form = document.forms.memberjoin;

  console.log($form.elements);           // 폼 필드들
  const $username = $form.username;
  console.log($username.value);
  $username.value = '유관순';

  const $gender = $form.gender;
  console.log($gender[1].checked);
  $gender[1].checked = true;

  const $age = $form.age;
  console.log($age.options);
  $age.options[2].selected = true;
  $age.selectedIndex = 3;
  $age.value = '50';

  const $introduce = $form.introduce;
  console.log($introduce.value);
  $introduce.value = 'value';
</script>

5-3-2. focus / blur / focusin / focusout

  • focus: 요소에 포커스가 들어올 때 (버블링 X)
  • blur: 요소에서 포커스가 빠져나갈 때 (버블링 X)
  • focusin, focusout: 포커스 이벤트가 버블링되는 버전
const $username = document.forms.memberjoin.username;

$username.onfocus = e => {
  e.target.classList.toggle('lightgray');
};

$username.onblur = e => {
  e.target.classList.toggle('lightgray');
};

// 폼 전체에 포커스 스타일 주고 싶을 때
const $form = document.forms.memberjoin;

$form.addEventListener('focusin', e => e.target.classList.add('focused'));
$form.addEventListener('focusout', e => e.target.classList.remove('focused'));

5-3-3. change vs input

  • change

    • select/checkbox/radio: 선택이 바뀌는 즉시 발생
    • text/textarea: 값 변경 후 포커스를 잃었을 때 발생
  • input

    • 값이 바뀌는 순간마다 발생
      (붙여넣기, 음성 입력 등 포함)
const $form = document.forms.memberjoin;
const $age = $form.age;
const $introduce = $form.introduce;

$age.addEventListener('change', () => alert('age change!'));
$introduce.addEventListener('change', () => alert('introduce change!'));

$introduce.addEventListener('input', e => {
  const len = e.target.value.trim().length;
  $form.querySelector('span').textContent = len;
});

5-4. 폼 제출 이벤트: submit

  • submit 은 폼이 서버로 전송되기 직전에 발생

  • 이 시점에 유효성 검사 → 통과 못하면 전송 취소 패턴을 많이 쓴다

  • 폼 전송 트리거

    • <button type="submit"> / <input type="submit">
    • 텍스트 입력 필드에서 Enter
<form
  method="GET"
  action="https://search.naver.com/search.naver"
  name="search"
>
  <input type="text" name="query" placeholder="검색할 키워드를 입력하세요">
  <button type="submit">네이버 검색하기</button>
</form>
<script>
  const $form = document.forms.search;

  $form.onsubmit = function () {
    const keyword = this.querySelector('input[name=query]').value;

    if (!keyword) {
      alert('검색어를 입력하지 않았다!');
      return false; // 기본 제출 동작 취소
      // 혹은 e.preventDefault() 사용도 가능
    }
  };
</script>

한 번에 정리

  • 핸들러 등록

    • 어트리뷰트: HTML 안에 onclick="..."
    • 프로퍼티: element.onclick = handler (한 개만 가능)
    • addEventListener: element.addEventListener(type, handler) (여러 개 가능)
  • 이벤트 객체

    • 첫 번째 인수로 자동 전달, 좌표/키 값/타깃 등 정보 포함
    • 프로퍼티/addEventListener 방식에서만 자유로운 변수명 사용
  • this

    • 일반 함수: this === e.currentTarget
    • 화살표 함수: 상위 스코프 this 캡처 (이벤트 요소 아님)
  • 전파 / 위임

    • 캡처링 → 타깃 → 버블링
    • 상위 요소에서 e.target 으로 하위 요소를 처리하는 패턴 = 이벤트 위임
  • 기본 동작 제어

    • e.preventDefault() 로 링크 이동, 폼 전송 등 막기
    • e.stopPropagation() 으로 위/아래 전파 차단
  • 폼 이벤트

    • focus / blur / focusin / focusout
    • change vs input
    • submit 에서 마지막 유효성 검사
profile
개발자 희망자

0개의 댓글