자료 유형: 무료 실무 해설팩
JavaScript 기본 문법은 배웠지만 실제 화면, 폼, 목록, API 코드에서 자주 막히는 학습자를 위한 실무 해설팩입니다. 단순 문법 설명이 아니라 버그로 이어지는 JavaScript 실수를 어떻게 발견하고 고치는지 기준을 제공합니다.
이 팩의 약속
이 자료는 var, let, const 같은 이론 나열이 아니다. 실제 서비스 화면에서 자주 터지는 실수를 중심으로 설명한다.
- 폼 입력값이 문자열이라 계산이 깨지는 문제
- 배열 원본을 바꿔 목록 화면이 예측 불가능해지는 문제
- 비동기 결과를 기다리지 않아 빈 화면 또는 이전 데이터가 보이는 문제
- DOM 이벤트를 중복 등록해 버튼이 여러 번 실행되는 문제
각 예제는 흔한 실패 코드 -> 왜 위험한지 -> 실무 화면에서 어떻게 터지는지 -> 개선 코드 -> 코드 리뷰 체크포인트 -> 다시 풀 문제 순서로 본다.
예제 1. 숫자처럼 보이는 문자열 계산 버그
연결 문제: https://nst21c.com/?task=sum#labApp
흔한 실패 코드
function calculateTotal(price, count) {
return price + count;
}
폼에서 받은 값은 대부분 문자열이다. price가 "10000"이고 count가 "2"이면 결과는 100002가 된다. 문제 풀이에서는 단순 덧셈 실수처럼 보이지만, 실무에서는 주문 금액, 수량, 할인 금액이 틀어지는 버그다.
왜 위험한가
JavaScript의 +는 숫자 덧셈과 문자열 연결을 모두 한다. 입력값의 출처가 input, URL query, localStorage, CSV, API라면 숫자처럼 보여도 문자열일 가능성이 높다.
개선 코드
function calculateTotal(price, count) {
const numericPrice = Number(price);
const numericCount = Number(count);
if (!Number.isFinite(numericPrice) || !Number.isFinite(numericCount)) {
return 0;
}
return numericPrice * numericCount;
}
실무 화면 예
- 장바구니 총액
- 관리자 매출 필터
- 쿠폰 할인 계산
- 페이지네이션 offset 계산
코드 리뷰 체크포인트
- 폼 입력값을 계산 전에 숫자로 변환했는가?
Number()결과가NaN일 때 처리하는가?- 금액/수량 계산에서 문자열 연결이 일어날 여지가 없는가?
변형 과제
- 빈 문자열이 들어오면 0으로 처리한다.
- 음수 수량이 들어오면 에러 메시지를 반환한다.
예제 2. 원본 배열 변경으로 목록 화면이 흔들리는 버그
연결 문제: https://nst21c.com/?task=arraySum#labApp
흔한 실패 코드
function getRecentOrders(orders) {
return orders.sort((a, b) => b.createdAt - a.createdAt).slice(0, 5);
}
sort()는 원본 배열을 바꾼다. 한 화면에서는 최신순으로 잘 보이지만, 다른 컴포넌트나 다음 필터에서 원래 순서를 기대하면 목록이 갑자기 바뀐다.
개선 코드
function getRecentOrders(orders) {
return [...orders]
.sort((a, b) => b.createdAt - a.createdAt)
.slice(0, 5);
}
실무 화면 예
- 주문 목록 정렬
- 검색 결과 필터
- 랭킹/추천 목록
- React state 배열 처리
코드 리뷰 체크포인트
sort,reverse,splice처럼 원본을 바꾸는 메서드를 알고 쓰는가?- 외부에서 받은 배열을 직접 수정하지 않는가?
- 정렬 결과와 원본 데이터의 책임이 분리되어 있는가?
변형 과제
- 최신순과 오래된순을 버튼으로 전환한다.
- 원본 배열의 첫 번째 항목이 정렬 후에도 유지되는지 테스트한다.
예제 3. 비동기 결과를 기다리지 않는 버그
연결 문제: https://nst21c.com/?task=fetchJson#labApp
흔한 실패 코드
function loadUsers() {
const users = fetch("/api/users").then((res) => res.json());
return users.length;
}
fetch()는 바로 사용자 배열을 주지 않는다. Promise를 반환한다. 그런데 Promise를 배열처럼 다루면 화면에는 빈 값이 보이거나 오류가 난다.
개선 코드
async function loadUsers() {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error("사용자 목록을 불러오지 못했습니다.");
}
const users = await response.json();
return users.length;
}
실무 화면 예
- 사용자 목록
- 상품 검색 결과
- 대시보드 통계 카드
- 자동완성 검색창
코드 리뷰 체크포인트
- Promise를 실제 데이터처럼 바로 사용하지 않는가?
await가 필요한 위치에 있는가?- 실패 응답을 성공 데이터처럼 처리하지 않는가?
변형 과제
try/catch로 실패 메시지를 반환한다.- 빈 배열일 때
"결과 없음"을 반환한다.
예제 4. 이벤트 리스너 중복 등록 버그
연결 문제: https://nst21c.com/?task=domButton#labApp
흔한 실패 코드
function setupButton() {
const button = document.querySelector("#save");
button.addEventListener("click", save);
}
setupButton();
setupButton();
같은 이벤트가 두 번 등록되면 버튼을 한 번 눌러도 저장 요청이 두 번 나간다. 개발 중에는 잘 안 보이다가 실제 서비스에서 중복 주문, 중복 신청, 중복 저장으로 이어질 수 있다.
개선 코드
function setupButton() {
const button = document.querySelector("#save");
if (!button || button.dataset.bound === "true") return;
button.dataset.bound = "true";
button.addEventListener("click", save);
}
실무 화면 예
- 저장 버튼
- 신청 폼 제출
- 모달 열기/닫기
- 무한 스크롤 이벤트
코드 리뷰 체크포인트
- 초기화 함수가 여러 번 실행될 가능성이 있는가?
- 이벤트를 등록하기 전에 기존 등록 상태를 확인하는가?
- 한 번만 실행되어야 하는 액션에 중복 방지가 있는가?
5일 복습 루틴
1일차: 숫자 문자열 계산 문제를 다시 풀고 입력값 출처를 적는다. 2일차: 배열 정렬 문제를 풀고 원본 변경 메서드를 정리한다. 3일차: fetch 문제를 풀고 Promise와 실제 데이터의 차이를 설명한다. 4일차: DOM 이벤트 문제를 풀고 중복 실행 방지 코드를 작성한다. 5일차: 네 예제를 하나의 “상품 목록 + 장바구니” 화면에 연결해본다.
이 팩 다음에 보낼 후속 안내
JavaScript 팩을 받은 사용자는 TypeScript로 넘어가기 전에 “값의 출처와 형태”를 계속 확인해야 한다. 후속 메일에서는 TypeScript의 unknown, optional 필드, API 응답 타입으로 연결한다.