JavaScript 강의록 — 이벤트 / BOM / 비동기

#js#event

1. 이벤트 핸들러 등록 / 제거

세 가지 방식 비교

방식특징권장 여부
어트리뷰트 onclick="..."HTML 안에 JS 혼재사용 금지
핸들러 프로퍼티 el.onclick = fn하나의 핸들러만 등록 가능 — 덮어씌워짐비권장
addEventListener하나의 이벤트에 여러 핸들러 등록 가능표준
// 방법 2: 두 번째 할당이 첫 번째를 덮어씀 $btn.onclick = () => console.log("첫 번째"); // 무시됨 $btn.onclick = () => console.log("두 번째"); // 이것만 실행 // 방법 3: 둘 다 실행됨 $btn.addEventListener("click", () => console.log("첫 번째")); $btn.addEventListener("click", () => console.log("두 번째"));

핸들러 제거

익명 함수는 참조를 보관할 수 없어 제거가 불가능하다. 제거할 핸들러는 반드시 변수에 저장한다.

const mission = () => alert("수행 완료"); $btn.addEventListener("click", mission); $btn.removeEventListener("click", mission); // 동일 참조여야 제거됨 // 익명 함수는 추가는 되지만 제거할 방법이 없다 $btn.addEventListener("click", () => alert("이 이벤트는 제거 불가")); $btn.removeEventListener("click", () => alert("이 이벤트는 제거 불가")); // 무의미

2. 이벤트 객체와 this

이벤트 객체 (e)

이벤트 발생 시 브라우저가 생성한 '사건 보고서'. 핸들러의 첫 번째 인자로 자동 전달된다.

프로퍼티설명
e.target이벤트가 실제로 발생한 요소
e.currentTarget이벤트 리스너가 부착된 요소
e.clientX / e.clientY클릭한 마우스 좌표
e.key / e.code누른 키 문자 / 물리적 키 코드
document.addEventListener("click", (e) => { console.log(e.clientX, e.clientY); // 클릭 좌표 console.log(e.target); // 실제 클릭된 요소 console.log(e.currentTarget); // 리스너가 부착된 요소 (여기선 document) });

this vs e.currentTarget

함수 종류this가 가리키는 것
일반 function이벤트를 부착한 요소 (= e.currentTarget)
화살표 함수상위 스코프의 this (= window 또는 undefined)

e.currentTarget은 함수 종류와 무관하게 항상 리스너가 부착된 요소를 가리킨다. this 대신 e.currentTarget을 사용하는 것이 버그를 줄이는 가장 안전한 습관이다.

// 권장 패턴 $btn.addEventListener("click", (e) => { const btn = e.currentTarget; // 화살표 함수에서도 안전하게 요소 참조 btn.style.backgroundColor = "skyblue"; });

3. 이벤트 전파와 위임

버블링

자식 요소에서 발생한 이벤트가 부모 → 조부모 → ... → window까지 전파되는 현상. 부모에 달린 핸들러는 자식 클릭에도 반응한다.

$parent.addEventListener("click", (e) => { console.log(e.target); // 실제로 클릭된 요소 (자식일 수 있음) console.log(e.currentTarget); // 리스너가 부착된 요소 (항상 $parent) });

이벤트 위임

버블링을 활용해 자식 각각에 핸들러를 달지 않고, 부모 하나에만 등록해 모든 자식의 이벤트를 처리하는 패턴. 동적으로 추가된 자식에도 자동으로 작동한다는 것이 핵심 장점이다.

// 각 li가 아닌 부모 ul에만 핸들러 등록 $ul.addEventListener("click", (e) => { if (e.target.matches("li")) { // 클릭된 것이 li일 때만 처리 e.target.classList.toggle("highlight"); } }); // 나중에 동적으로 추가된 li에도 위 핸들러가 자동 적용됨 const $newLi = document.createElement("li"); $newLi.textContent = "사이다"; $ul.appendChild($newLi);

4. 이벤트 제어

preventDefault — 기본 동작 막기

브라우저가 이벤트에 대해 갖고 있는 '기본 동작'을 취소한다. 대표적인 사례는 폼 제출 시 페이지 새로고침 방지다.

$form.addEventListener("submit", (e) => { e.preventDefault(); // 새로고침 막기 // 유효성 검사 수행 if ($usernameInput.value.trim() === "") { $errorMessage.textContent = "아이디를 입력해주세요!"; return; } console.log(`서버로 제출할 아이디: ${$usernameInput.value}`); });

기본 동작이 있는 주요 이벤트 예시

이벤트브라우저 기본 동작
form submit페이지 새로고침 (서버 전송)
a 태그 click지정된 href로 페이지 이동
contextmenu브라우저 우클릭 메뉴 표시

stopPropagation — 버블링 막기

이벤트가 부모로 전파되는 것을 차단한다. 모달 내부 클릭이 배경 클릭으로 전파돼 모달이 닫히는 상황이 대표적인 사례다.

// 배경 클릭 시 모달 닫기 $modalBackground.addEventListener("click", () => { $modalBackground.style.display = "none"; }); // 모달 내부 클릭은 배경까지 전파되지 않도록 차단 $modalContent.addEventListener("click", (e) => { e.stopPropagation(); });

5. 마우스 / 키보드 이벤트

마우스 이벤트

이벤트발생 시점
click왼쪽 버튼 눌렀다 뗐을 때
dblclick더블클릭
mousedown버튼 누르는 순간 (e.button: 0=좌, 1=중, 2=우)
mouseup버튼 떼는 순간
contextmenu우클릭 (기본 메뉴 차단 시 preventDefault 사용)
mousemove커서 이동할 때마다
$btn.addEventListener("mousedown", (e) => { console.log(e.button); // 0: 좌클릭, 2: 우클릭 }); $btn.addEventListener("contextmenu", (e) => { e.preventDefault(); // 브라우저 기본 우클릭 메뉴 차단 });

키보드 이벤트

이벤트발생 시점
keydown키를 누르는 순간 (누르고 있는 동안 계속 발생)
keyup키에서 손을 떼는 순간
프로퍼티예시설명
e.key'a', 'A', 'Enter'입력된 문자 (대소문자 구분)
e.code'KeyA', 'Enter'물리적 키 위치 코드
$input.addEventListener("keydown", (e) => { console.log(e.key, e.code); if (e.key === "Enter") { // Enter 키 입력 시 검색 실행 } });

6. 폼 이벤트

폼 요소 접근

const $form = document.forms.joinForm; // name 속성으로 폼 접근 const $username = $form.username; // 폼 내 요소도 name으로 접근 const $hobbies = $form.hobby; // 동일 name → NodeList 반환

폼 관련 이벤트 정리

이벤트발생 시점주요 용도
focus요소에 포커스 진입입력 필드 강조 스타일
blur요소에서 포커스 이탈입력값 유효성 검사
input값 변경될 때마다 (실시간)글자 수 카운터, 실시간 검색
change값 변경 후 포커스 이탈셀렉트박스, 체크박스 반영
submit폼 제출 버튼 클릭유효성 검사 후 서버 전송

submit 유효성 검사 패턴

$form.addEventListener("submit", (e) => { // 1. 이름 유효성 검사 if ($username.value.trim().length < 2) { e.preventDefault(); $error.textContent = "이름은 두 글자 이상 입력해주세요."; return; } // 2. 체크박스 유효성 검사 let checkedCount = 0; $hobbies.forEach((hobby) => { if (hobby.checked) checkedCount++; }); if (checkedCount < 2) { e.preventDefault(); $error.textContent = "관심사는 2개 이상 선택해주세요."; return; } // 모든 검사 통과 시 기본 동작(서버 전송)이 실행됨 });

7. BOM — 브라우저 제어

window는 브라우저의 최상위 전역 객체. 생략 가능 — alert()는 실제로 window.alert()다.

대화 상자

메서드반환값설명
alert(msg)없음메시지 표시
confirm(msg)boolean확인/취소 → true / false
prompt(msg, default)string | null텍스트 입력받기
const answer = confirm("결제를 진행하시겠습니까?"); if (answer) { /* 확인 */ } else { /* 취소 */ } const name = prompt("이름을 입력하세요", "홍길동"); if (name) console.log(`반갑습니다. ${name}님.`);

location 객체 — 페이지 이동

사용법특징
location.href = url이동 — 방문 기록에 남음 (뒤로 가기 가능)
location.replace(url)이동 — 방문 기록 대체 (뒤로 가기 불가)
location.reload()현재 페이지 새로고침

history 객체 — 방문 기록 제어

history.back(); // 뒤로 가기 history.forward(); // 앞으로 가기 history.go(-2); // 2 페이지 뒤로

8. 비동기 프로그래밍

동기 vs 비동기

구분특징문제점
동기 (Synchronous)앞 작업이 끝날 때까지 다음 작업 대기오래 걸리는 작업 시 페이지 전체 블로킹
비동기 (Asynchronous)오래 걸리는 작업을 브라우저에 위임 후 즉시 다음 코드 실행결과 순서 예측이 어려움

타이머 함수

함수동작취소
setTimeout(fn, ms)ms 후 fn을 딱 한 번 실행clearTimeout(id)
setInterval(fn, ms)ms 간격으로 fn을 반복 실행clearInterval(id)
const timerId = setTimeout(() => { console.log("3초 후 실행"); }, 3000); clearTimeout(timerId); // 실행 전에 취소 가능 let count = 0; const intervalId = setInterval(() => { console.log(`${count}초 경과`); if (count++ === 5) clearInterval(intervalId); }, 1000);

이벤트 루프 동작 원리

자바스크립트는 싱글 스레드지만, 브라우저가 비동기 작업을 대신 처리해주는 구조로 동시성을 구현한다.

구성요소역할
Call Stack코드가 실행되는 단일 스택
Web APIsetTimeout, fetch 등 오래 걸리는 작업을 브라우저가 처리
Task QueueWeb API 완료 후 콜백이 대기하는 줄
Event LoopCall Stack이 비면 Task Queue에서 콜백을 꺼내 실행
console.log("시작"); setTimeout(() => console.log("타이머"), 0); // 0ms여도 Queue로 이동 console.log("끝"); // 출력 순서: 시작 → 끝 → 타이머

setTimeout(fn, 0)이라도 콜백은 반드시 현재 Call Stack이 모두 비워진 후에 실행된다.