티스토리 뷰
JavaScript에서 흔하게 하는 실수를 정리해보자!
async/await 에러 처리 누락
에러 처리가 누락되면 에러가 조용히 사라지기 때문에 꼭 처리
// 에러가 나도 아무 일도 안 일어남
async function fetchUser() {
const res = await fetch("/api/user");
return res.json();
}
// try/catch로 명확하게
async function fetchUser() {
try {
const res = await fetch("/api/user");
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
return res.json();
} catch (error) {
console.error("유저 조회 실패:", error);
throw error;
}
}
참고로 Promise.all의 경우 하나만 실패해도 전체가 터지기 때문에 Promise.allSettled를 사용하자
// 하나 실패하면 전체 실패
const [user, post] = await Promise.all([fetchUser(), fetchPost()]);
// 각각의 성공/실패를 개별로 처리
const results = await Promise.allSettled([fetchUser(), fetchPost()]);
results.forEach(result => {
if (result.status === "fulfilled") console.log(result.value);
if (result.status === "rejected") console.error(result.reason);
});
객체/배열 직접 변경
원본을 직접 변경하면 어디서 변경되었는지 추적이 어렵고, 반응성이 깨지는 원인이 된다.
// 원본 배열을 직접 변경
const addItem = (items, newItem) => {
items.push(newItem);
return items;
};
const updateUser = (user, name) => {
user.name = name;
return user;
};
// 새 객체를 반환
const addItem = (items, newItem) => [...items, newItem];
const updateUser = (user, name) => ({ ...user, name });
== 사용
==는 타입을 자동으로 변환하기 때문에 예상하지 못한 결과를 만들어 낸다.
// == 사용 (암묵적 타입 변경)
0 == false // true
"" == false // true
null == undefined // true
0 == "0" // true
// === 사용
0 === false // false
null === undefined // false
다만 null 체크할 때는 ==를 쓰는 경우가 많다.
// null과 undefined를 동시에 체크하는 경우
if (value == null) { ... }
클로저에서 루프 변수 참조
var로 선언한 루프 변수를 클로저에서 참조하면 의도와 다르게 동작한다.
// 모두 3을 출력
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// let을 쓰면 블록 스코프로 의도대로 0, 1, 2 출력
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
부동소수점 연산 그대로 사용
JavaScript의 숫자는 IEEE 754 부동소수점이라 소수 계산이 정확하지 않아 금액 계산에서 틀어지는 경우가 많다.
// 예상과 다른 결과
0.1 + 0.2 === 0.3 // false
0.1 + 0.2 // 0.30000000000000004
// 정수로 변환해서 계산
const add = (a, b) => Math.round((a + b) * 100) / 100;
add(0.1, 0.2); // 0.3
this 바인딩 실수
콜백 함수 안에서 this를 참조하면 this가 window 또는 undefined이 되기 때문에 화살표 함수를 사용하는게 좋다.
// 콜백 안의 this는 컴포넌트를 가리키지 않음
export default {
data() {
return { count: 0 };
},
methods: {
startTimer() {
setTimeout(function () {
this.count++; // this가 undefined 또는 window
}, 1000);
}
}
}
// 화살표 함수를 쓰면 this가 유지됨
startTimer() {
setTimeout(() => {
this.count++; // 컴포넌트의 this
}, 1000);
}
얕은 복사 vs 깊은 복사
{...obj} 로 복사해도 중첩 객체는 여전히 같은 참조를 가리키기 때문에 어디서 변경되었는지 추적이 어렵다.
const user = {
name: "현지",
address: { city: "서울" }
};
// 얕은 복사
const copy = { ...user };
copy.address.city = "부산";
console.log(user.address.city); // "부산" — 원본도 바뀜
// 깊은 복사
const deepCopy = JSON.parse(JSON.stringify(user));
deepCopy.address.city = "부산";
console.log(user.address.city); // "서울" — 원본 유지
복잡한 객체의 경우 최신 브라우저에서는 structuredClone() 을 사용하면 된다.
const deepCopy = structuredClone(user);
setTimeout / setInterval 정리 누락
컴포넌트가 사라져도 타이머가 계속 돌아가면 메모리 누수가 생기기 때문에 정리해야 한다.
import { onMounted, onUnmounted } from 'vue';
onMounted(() => {
const timer = setInterval(fetchData, 3000);
onUnmounted(() => clearInterval(timer));
});'JavaScript' 카테고리의 다른 글
| TypeScript 활용 팁 (0) | 2026.02.23 |
|---|---|
| Javascript - ECMAScript 버전 히스토리 (0) | 2026.02.05 |
| 메모리 누수 원인 (0) | 2026.02.03 |
| JavaScript - Canvas 차트 모바일 화질 저하 문제 (0) | 2024.04.26 |
| JavaScript - 전화번호에 하이픈 추가 (0) | 2024.03.28 |