JavaScript Mistakes
JavaScript Mistakes: 흔히 발생하는 오류와 해결 방법
JavaScript는 매우 유연하고 강력한 언어이지만, 그 특성상 실수를 저지르기 쉽습니다. 특히 비동기 작업, 변수 스코프, 타입 변환 등과 관련된 실수는 자주 발생합니다. 이러한 실수는 코드 오류나 예상치 못한 동작을 유발할 수 있습니다. 이 가이드는 JavaScript에서 자주 저지르는 실수들과 이를 방지하거나 수정하는 방법을 다룹니다.
1. var
대신 let
과 const
를 사용하지 않음
- *
var
*는 함수 스코프를 가지며, 호이스팅(변수 선언이 코드 상단으로 끌어올려지는 현상)으로 인해 예기치 못한 동작을 일으킬 수 있습니다. 이를 방지하기 위해 **let
*과 **const
*를 사용하는 것이 좋습니다.
1.1. 문제: var
의 호이스팅
function example() {
console.log(x); // undefined
var x = 10;
}
example();
var
는 호이스팅 때문에 선언되기 전에 undefined
값으로 초기화됩니다.
1.2. 해결: let
과 const
사용
function example() {
console.log(x); // ReferenceError: x is not defined
let x = 10;
}
example();
- *
let
*과 **const
*는 호이스팅되지만, 초기화되기 전에는 접근할 수 없습니다. 따라서ReferenceError
를 발생시켜 논리 오류를 방지할 수 있습니다.
2. 엄격한 비교(===
)를 사용하지 않음
- *
==
*는 느슨한 비교를 하여 타입을 자동으로 변환한 후 값을 비교합니다. 이로 인해 예상치 못한 결과가 발생할 수 있습니다. **===
*를 사용하여 엄격한 타입 비교를 하는 것이 좋습니다.
2.1. 문제: 느슨한 비교(==
)
console.log(0 == false); // true
console.log(null == undefined); // true
- *
==
*는 타입을 자동으로 변환하여 비교하기 때문에, 논리적으로 다를 수 있는 값들도 같다고 판단할 수 있습니다.
2.2. 해결: 엄격한 비교(===
) 사용
console.log(0 === false); // false
console.log(null === undefined); // false
- *
===
*는 타입 변환을 하지 않고 값과 타입을 모두 비교하므로, 보다 명확한 결과를 제공합니다.
3. 비동기 작업에서 return
을 사용하지 않음
비동기 작업에서 데이터를 반환할 때 **return
**을 사용하지 않으면, Promise가 예상대로 동작하지 않을 수
있습니다. 비동기 함수는 항상 Promise를 반환하므로, 데이터를 반환할 때 return
키워드를 명시해야 합니다.
3.1. 문제: return
을 빠뜨림
async function getData() {
fetch('<https://api.example.com/data>');
}
const data = getData();
console.log(data); // Promise {<pending>}
위 코드에서는 **fetch
**의 반환 값을 반환하지 않아서 data
는 항상 Promise 객체가
됩니다.
3.2. 해결: return
명시
async function getData() {
return fetch('<https://api.example.com/data>');
}
getData().then(response => console.log(response));
- *
return
*을 명시하면getData()
함수가 Promise를 반환하고, **.then()
*을 사용하여 비동기 결과를 처리할 수 있습니다.
4. this
바인딩 문제
JavaScript에서 **this
**는 함수가 어떻게 호출되었는지에 따라 달라집니다. 특히 화살표 함수와 일반
함수에서 this
가 다르게 동작할 수 있기 때문에, this
를 사용할 때 주의가 필요합니다.
4.1. 문제: this
가 전역 객체를 참조
const person = {
name: 'Alice',
greet: function() {
setTimeout(function() {
console.log(`Hello, ${this.name}`); // undefined
}, 1000);
}
};
person.greet();
위 예제에서 일반 함수의 this
는 전역 객체를 참조하므로, **this.name
**은
undefined입니다.
4.2. 해결: 화살표 함수로 this
를 상속
const person = {
name: 'Alice',
greet: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}`); // Hello, Alice
}, 1000);
}
};
person.greet();
화살표 함수는 상위 스코프의 this
를 상속받으므로, **this.name
**이 올바르게 **Alice
**를
참조하게 됩니다.
5. for...in
과 for...of
의 혼동
- *
for...in
*은 객체의 **열쇠(key)**를 순회하고, **for...of
*는 **값(value)**를 순회합니다. 배열에서는 **for...in
*을 사용하는 것이 바람직하지 않으며, 대신 **for...of
*를 사용해야 합니다.
5.1. 문제: 배열에서 for...in
사용
const arr = [10, 20, 30];
for (let index in arr) {
console.log(index); // 0, 1, 2 (값이 아니라 인덱스가 출력됨)
}
- *
for...in
*은 배열에서 인덱스를 순회하므로, 값이 아닌 **키(key)**가 출력됩니다.
5.2. 해결: 배열에서 for...of
사용
const arr = [10, 20, 30];
for (let value of arr) {
console.log(value); // 10, 20, 30 (값이 출력됨)
}
- *
for...of
*는 배열의 값을 순회하므로, 실제 값을 다룰 수 있습니다.
6. parseInt()
를 사용할 때 기수(Radix)를 지정하지 않음
parseInt()
함수는 문자열을 숫자로 변환할 때 **기수(radix)**를 지정하지 않으면, 특정 상황에서 잘못된 변환이
발생할 수 있습니다. 항상 기수를 명시하는 것이 좋습니다.
6.1. 문제: 기수 미지정
const num = parseInt('08'); // 출력: 0 (8진수로 처리됨)
기수를 명시하지 않으면, 08
같은 숫자는 8진수로 해석될 수 있습니다.
6.2. 해결: 기수 지정
const num = parseInt('08', 10); // 출력: 8 (10진수로 처리됨)
기수 10을 명시하여 10진수로 처리되도록 합니다.
7. 클로저로 인해 발생하는 메모리 누수
- *클로저(closure)**는 함수가 외부 함수의 변수에 접근할 수 있는 구조입니다. 하지만 클로저가 불필요하게 많은 메모리를 참조하면 메모리 누수가 발생할 수 있습니다. 클로저를 사용한 후에는 명시적으로 해제해야 합니다.
7.1. 문제: 클로저로 인한 메모리 누수
function createCounter() {
let count = 0;
return function increment() {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
// 하지만 count는 여전히 메모리에 남아 있음
클로저는 **count
**를 참조하기 때문에, 필요하지 않다면 이를 해제해야 합니다.
7.2. 해결: 필요할 때 클로저 해제
function createCounter() {
let count = 0;
return function increment() {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter = null; // 클로저 해제
8. 정수 나눗셈에서 예상치 못한 결과 발생
JavaScript는 정수 나눗셈과 실수 나눗셈을 구분하지 않습니다. 소수점 이하의 값이 자동으로 처리되
기 때문에, 정수만 필요한 경우에는 주의해야 합니다.
8.1. 문제: 정수 나눗셈에서 소수점 처리
let result = 10 / 3;
console.log(result); // 출력: 3.3333333333333335
정수 나눗셈에서도 소수점 이하 값이 출력됩니다.
8.2. 해결: Math.floor()
사용
let result = Math.floor(10 / 3);
console.log(result); // 출력: 3
- *
Math.floor()
*를 사용하여 소수점 이하 값을 제거하고 정수만 취합니다.
9. NaN
을 잘못 처리
- *
NaN
(Not-a-Number)**는 JavaScript에서 특별한 숫자 값입니다. 그러나NaN
과의 비교는 항상 **false
*를 반환하기 때문에,isNaN()
함수로 처리하는 것이 안전합니다.
9.1. 문제: NaN
비교
console.log(NaN === NaN); // false
- *
NaN
*은 자기 자신과도 같지 않음을 나타냅니다.
9.2. 해결: isNaN()
사용
console.log(isNaN(NaN)); // true
isNaN()
함수를 사용하면 NaN
값을 안전하게 처리할 수 있습니다.
10. 함수 호출 시 this
를 명시적으로 바인딩하지 않음
JavaScript에서 함수 호출 시 **this
**가 의도하지 않게 바인딩될 수 있습니다. 특히, 이벤트 핸들러나 콜백 함수에서는
bind()
, call()
, 또는 **apply()
**로 명시적으로
this
를 설정해야 합니다.
10.1. 문제: this
바인딩 누락
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};
setTimeout(obj.greet, 1000); // undefined
위 코드에서는 **this
**가 전역 객체를 참조하므로, **undefined
**가 출력됩니다.
10.2. 해결: bind()
로 바인딩
setTimeout(obj.greet.bind(obj), 1000); // Alice
- *
bind()
*를 사용하여 **this
*를 명시적으로 바인딩합니다.
요약
- *
let
과const
*를 사용하여 변수 스코프 문제를 해결하고, **엄격한 비교(===
)**로 예상치 못한 타입 변환을 방지합니다. - 비동기 함수에서 **
return
*을 명시하여 Promise의 결과를 처리하고, 이벤트 핸들러나 콜백에서this
를 올바르게 바인딩합니다. - *
for...of
*와 **for...in
*의 차이를 이해하고 적절하게 사용하며, **기수(radix)**를 지정하여 숫자를 정확하게 파싱합니다. - 클로저를 사용한 후에는 불필요한 참조를 해제하고,
isNaN()
함수를 통해 NaN 값을 안전하게 처리합니다.
이러한 JavaScript 실수와 해결 방법을 이해하고 적용하면 코드 안정성과 유지보수성이 크게 향상됩니다.