본문 바로가기
부트캠프

멋쟁이사자처럼 프론트엔드 부트캠프 14기 - Day 36 (이벤트, 콜백, 이벤트 루프, 콜백과 비동기 코드)

by 나른한_꼬리_ 2025. 6. 16.
반응형

멋쟁이사자처럼 프론트엔드 부트캠프 14기 - Day 36 (이벤트, 콜백, 이벤트 루프, 콜백과 비동기 코드)

 

목차

 

이벤트(Event)

JavaScript에서 이벤트(Event)어떤 일이 발생했음을 알려준다. 예를 들어, 마우스 포인터로 클릭(click)하면 클릭 이벤트가 발생하고, 키보드의 키(key)를 누르면 키보드 이벤트가 발생한다.

 

 

이벤트 리스너

이벤트를 수신하려면 이벤트 리스너(Event Listener, 청취자)가 필요하다.

element.addEventListener('type', () => {})
// 'type': 이벤트의 타입의 이름
// () => {}: 이벤트가 발생할 때마다 호출되는 함수(이벤트 리스너)

 

예를 들어, <button> 요소에서 발생하는 클릭 이벤트를 리스닝하고 싶다면 다음과 같이 사용할 수 있다.

<button type="button" class="clickable">클릭 가능한 버튼 요소</button>

<script>
  const button = document.querySelector('button.clickable')

  button.addEventListener('click', () => {
    console.log('클릭 가능한 버튼 요소를 눌렀습니다.')
  })
</script>

 

이제 웹 페이지의 <button> 요소를 마우스로 클릭하면 콘솔 패널에 메시지가 출력된다.

 

이벤트를 청취 중인 요소인지 확인하려면 콘솔 패널에 getEventListeners(button) 명령을 입력한다. 해당 명령은 콘솔 패널에 직접 입력해야지만 실행된다.

 

이벤트로 DOM 조작

이벤트 리스너로 DOM 요소를 조작할 수 있다.

body {
  background-color: #fff;
  &.is-clicked {
    background-color: #000;
  }
}

button {
  background-color: #000;
  &.is-clicked {
    background-color: #fff;
  }
}

 

위와 같이 CSS를 선언하고, <button> 요소를 클릭했을 때 배경 색을 변경하는 JavaScript 코드는 다음과 같다.

const button = document.querySelector('button.clickable')

button.addEventListener('click', () => {
  document.body.classList.add('is-clicked')
  button.classList.add('is-clicked')
})

 

마찬가지로, <button> 요소를 클릭할 때마다 배경 색이 전환되도록 하려면 조건문을 사용하거나 classList.toggle() 메서드를 활용할 수 있다.

const button = document.querySelector('button.clickable')

button.addEventListener('click', () => {
  if (!button.classList.contains('is-clicked')) {
	  document.body.classList.add('is-clicked')
	  button.classList.add('is-clicked')
  } else {
    document.body.classList.remove('is-clicked')
	  button.classList.remove('is-clicked')
  }
})
const button = document.querySelector('button.clickable')

button.addEventListener('click', () => {
  document.body.classList.toggle('is-clicked')
  button.classList.toggle('is-clicked')
})

 

레거시 이벤트 모델

레거시(legacy)란 옛날 방식 또는 이전 버전이라는 뜻이다. 웹 초창기에는 .addEventListener() 메서드를 지원하지 않았고, HTML 요소에 직접 onclick 속성을 추가하여 함수 실행 구문을 문자열 값으로 설정했다.

 

아래는 레거시 방법이다.

<!-- 방법1 -->
<button type="button" onclick="alert('버튼이 클릭됐어요!')">알림</button>

<!-- 방법2 -->
<button type="button" onclick="handleClick()">알림</button>
<script>
  function handleClick() {
    alert('버튼이 클릭됐어요!')
  }
</script>

 

레거시 방법을 사용하면 다음과 같은 특징과 한계가 있다.

  1. HTML과 JavaScript가 섞인다. 코드가 섞여서 코드가 지저분해지고 관리가 어려워질 수 있다.
  2. 재사용과 유지보수가 불리해진다. 동일하게 여러 곳에서 작동되게 하려면, 태그마다 함수 실행 구문을 넣어야 한다.

 

따라서, 레거시 이벤트 모델은 아주 간단한 테스트나 빠른 시연에는 쓸 수 있지만, 실제 프로젝트나 규모가 있는 개발에서는 권장되지 않는다. 유지보수와 코드 관리, 확장성을 위해 최신 이벤트 모델을 사용하는 것이 좋다.

 

콜백(Callback)

콜백(Callback) 나중에 실행되기 위해 다른 함수에 인수로 전달되는 함수를 말한다. 콜백이 자주 사용되는 예시로는 이벤트 리스너가 있다. 이벤트 리스너는 아래처럼 변수에 참조된 함수로 설정할 수 있다.

function callback() {
  console.log('콜백은 "다른 함수에 인수로 전달된 함수"를 말합니다.')
}

element.addEventListener('click', callback)

 

콜백이 유용한 상황은 다음과 같다.

  1. 유연하게 코드 추가
  2. 비동기 코드 작업 차단 방지

 

 

유연하게 코드 추가

요소에 이벤트 리스너를 추가하려면 콜백을 전달받고, 전달된 콜백 함수는 이벤트가 발생하면 실행된다. 동일한 요소에 또 다른 이벤트 리스너를 추가하는 것도 가능하다.

// 첫 번째 이벤트 리스너
element.addEventListener('click', () => {
  console.log('전달된 콜백 함수는 이벤트가 발생하면 실행됩니다.')
})
// 두 번째 이벤트 리스너
element.addEventListener('click', () => {
	console.log('동일한 요소에 같은 메서드일지라도 원하는 다른 코드를 실행하도록 설정하기 쉽습니다.')
})

 

단, 레거시 이벤트 모델은 변수 선언이기 때문에 하나의 값만 할당할 수 있다. 동일한 변수에 값을 두 번 할당하면 가장 마지막에 할당한 값만 남는다.

 

비동기 코드 작업 차단 방지

동기(Sync) 방식의 코드는 위에서 아래로 순차적으로 실행되는 코드를 말한다. 먼저 작성한 라인의 코드가 처리될 때까지 기다렸다가 다음 라인의 코드를 처리한다.

 

반면, 비동기(Async) 방식의 코드는 순차적으로 실행하지 않으며, 트리거가 작동되면 실행한다. 비동기 방식의 코드 예로 이벤트 리스너를 들 수 있다. 아래 코드를 보면 클릭하기 전까지는 콜백이 실행되지 않는다.

element.addEventListener('click', () => {
  console.log('사용자가 요소를 클릭해야만 이 메시지가 표시됩니다.')
})

 

JavaScript는 한 번에 한 가지 일만 할 수 있는 싱글 스레드(Single Thread) 언어다. 한 가지 일이 완료될 때까지 다른 일을 처리하지 못한다. JavaScript가 무언가 처리하고 있는 중에 다른 일을 요청해도 JavaScript는 응답하지 않는다. 이것을 차단(Blocking)이라고 한다.

 

이벤트 루프

JavaScript는 싱글 스레드로 작동해서 한 번에 하나의 작업만 실행할 수 있다. 하나의 함수가 실행되면, 해당 함수가 종료되기 전까지 다른 어떤 작업도 중간에 수행할 수 없다. 하지만 일반적으로 JavaScript가 사용되는 환경을 고려하면 동시에 많은 작업들이 처리되기도 한다. 다행히 웹 브라우저는 JavaScript 엔진이 제공하지 않는 Web API를 제공한다. Web API를 사용하면 비동기 처리 방식으로 코드가 블로킹되는 것을 방지할 수 있다.

 

  • 콜 스택(Call Stack): 함수를 호출하면 콜 스택에 쌓인다. 콜 스택은 마지막에 들어온 것이 먼저 나가는 LIFO(Last-In First-Out) 구조 형태로 쌓인다. (접시 쌓기. 아래부터 쌓이고 위부터 꺼냄)
  • Web API: 브라우저가 제공하는 함수들을 포함한 Web API는 메인 스레드(Main Thread)를 차단하지 않는다. 트리거가 발생할 때까지 담고 있는다.
  • 큐(Queue): Web API에서 트리거가 발생하면 콜스택에 추가되지 않고 큐에 전달된다. 큐는 먼저 들어온 것이 먼저 나가는 FIFO(First-In First-Out) 구조 형태의 집합니다. 콜 스택이 빌 때까지 큐에서 기다리다가 콜 스택이 비면 콜 스택으로 이동한다.

 

아래와 같은 코드를 콘솔에 입력하면 다음과 같은 일이 일어난다.

const foo = () => console.log("First")
const bar = () => setTimeout(() => console.log("Second"), 500)
const baz = () => console.log("Third")

bar()
foo()
baz()

 

이벤트 루프의 순서:

  1. bar() → 콜 스택에 추가된 후 실행
  2. bar() 실행 결과 setTimeout() 반환
  3. setTimeout() 함수가 실행되고, 콜백 함수 Web API로 이동
  4. foo() → 콜 스택에 추가된 후 실행
  5. foo() 실행 결과 "First" 출력
  6. baz() → 콜 스택에 추가된 후 실행
  7. baz() 실행 결과 "Third" 출력
  8. 콜백 함수 → 큐(대기열)로 이동
  9. 이벤트 루프(Event Loop) 발동
  10. 콜백 함수 → 콜 스택에 추가된 후 실행
  11. 콜백 함수 실행 결과 "Second" 출력

 

콜백과 비동기 코드

콜백을 사용하면 전체 작업을 중단하지 않고도 해야할 일을 JavaScript에 알릴 수 있다. 요소에 이벤트 리스너를 추가하면 설정된 콜백은 JavaScript에 의해 대기열에 추가한 후, 다른 작업을 진행한다. 이벤트가 발동되면 JavaScript는 대기열의 콜백을 실행한다. 그리고 다른 작업을 이어서 진행한다.

 

 

오늘 하루를 돌아보며

지난 주 과제 리뷰를 살짝 기대했는데, 다음주 과제부터 리뷰해 주신다고 한다. 아쉽긴 하지만 변수와 함수에 대해 고민할 수 있었고, 함수를 만드는 연습도 할 수 있었다.

 

JavaScript는 한 번에 한 가지 일만 할 수 있기 때문에 비동기 방식의 처리가 중요하고, 콜백 사용이 필요하다. 비동기 작업은 JavaScript를 이해하는 데 매우 중요한 개념이라고 했다. 한 번에 여러 일을 하는 멀티 태스킹이 안 되는 나처럼 JavaScript도 한 번에 하나만 할 수 있다. 그래서 필요한 게 콜백이고, Web API를 사용하여 비동기 방식의 처리가 중요한 것이다.

 

반응형