[] == falsetrue인데, [] == truefalse입니다. 도대체 빈 배열은 참인가요, 거짓인가요?

자바스크립트의 == 연산자는 비교 전에 암묵적 형변환을 수행합니다. 이 변환 규칙을 이해하지 못하면 조건문에서 의도와 다른 분기가 실행되는 버그를 만들게 됩니다.

원시 타입 7종

자바스크립트에는 7개의 원시 타입(Primitive Type) 이 있습니다.

타입예시비고
string'hello'불변(immutable)
number42, 3.14, NaN, InfinityIEEE 754 부동소수점
booleantrue, false
undefinedundefined선언만 하고 할당하지 않은 변수
nullnull"의도적으로 비어있음"
symbolSymbol('id')ES2015, 유일한 값
bigint9007199254740993nES2020, 임의 정밀도 정수

원시 타입의 핵심 특징은 불변(immutable) 이라는 점입니다. 값 자체를 변경할 수 없고, 새 값을 할당하는 것뿐입니다.

JS
let str = 'hello';
str[0] = 'H';       // 무시됨
console.log(str);    // 'hello' — 변하지 않음

str = 'Hello';       // 새 문자열을 할당한 것

참조 타입

원시 타입이 아닌 모든 것은 참조 타입(Reference Type) 입니다. 객체, 배열, 함수 등이 여기에 해당합니다.

JS
const a = { name: 'Alice' };
const b = a;            // 참조 복사
b.name = 'Bob';
console.log(a.name);    // 'Bob' — 같은 객체를 가리킴

원시 타입은 값을 복사하고, 참조 타입은 참조(주소)를 복사합니다. 깊은 복사와 얕은 복사의 차이를 이해하려면 이 구분이 기초가 됩니다.

typeof 연산자

typeof는 피연산자의 타입을 문자열로 반환합니다.

JS
typeof 'hello'     // 'string'
typeof 42          // 'number'
typeof true        // 'boolean'
typeof undefined   // 'undefined'
typeof Symbol()    // 'symbol'
typeof 42n         // 'bigint'
typeof {}          // 'object'
typeof []          // 'object' — 배열도 객체
typeof null        // 'object' — 유명한 버그!
typeof function(){} // 'function'

typeof null === 'object'

이것은 자바스크립트 초기 구현의 버그입니다. 첫 번째 버전에서 값의 타입을 하위 비트로 판별했는데, null은 값이 0이어서 객체로 판별되었습니다. 호환성 때문에 지금까지 수정되지 않았습니다.

null 체크에는 === null을 사용합니다.

JS
const value = null;

// 잘못된 방법
typeof value === 'null' // 이런 타입은 없음

// 올바른 방법
value === null // true

instanceof 연산자

instanceof는 프로토타입 체인을 따라가면서 타입을 확인합니다.

JS
[] instanceof Array   // true
[] instanceof Object  // true — Array의 프로토타입 체인에 Object가 있음
'' instanceof String  // false — 원시 타입은 객체가 아님

typeof는 원시 타입에, instanceof는 객체 타입에 적합합니다.

정확한 타입 체크

가장 정확한 방법은 Object.prototype.toString.call()입니다.

JS
Object.prototype.toString.call('hello')    // '[object String]'
Object.prototype.toString.call([])         // '[object Array]'
Object.prototype.toString.call(null)       // '[object Null]'
Object.prototype.toString.call(undefined)  // '[object Undefined]'

배열 체크에는 Array.isArray()가 가장 깔끔합니다.

JS
Array.isArray([]);  // true
Array.isArray({});  // false

암묵적 형변환 (Type Coercion)

자바스크립트는 타입이 다른 값끼리 연산할 때 자동으로 형변환 을 수행합니다. 이것을 암묵적 형변환(coercion)이라고 합니다.

문자열 변환

+ 연산자에서 한쪽이 문자열이면 나머지도 문자열로 변환됩니다.

JS
'5' + 3      // '53' — 숫자가 문자열로 변환
'5' + true   // '5true'
'5' + null   // '5null'
'5' + undefined // '5undefined'

숫자 변환

-, *, /, % 연산자는 피연산자를 숫자로 변환합니다.

JS
'5' - 3      // 2
'5' * '3'    // 15
true + 1     // 2 — true → 1
false + 1    // 1 — false → 0
null + 1     // 1 — null → 0
undefined + 1 // NaN — undefined → NaN

불리언 변환

조건문에서 자동으로 불리언 변환이 일어납니다.

truthy와 falsy

falsy 값 은 불리언 변환 시 false가 되는 값입니다. 나머지는 전부 truthy 입니다.

falsy 값 (전부 외우기)

JS
false
0
-0
0n       // BigInt의 0
''       // 빈 문자열
null
undefined
NaN

이 8개가 전부입니다. 나머지는 모두 truthy입니다.

자주 틀리는 truthy 값

JS
// 전부 truthy!
Boolean('0')        // true — 비어 있지 않은 문자열
Boolean('false')    // true
Boolean([])         // true — 빈 배열
Boolean({})         // true — 빈 객체
Boolean(new Boolean(false)) // true — 객체는 항상 truthy

빈 배열 []이 truthy라는 점은 자주 혼동되는 포인트입니다.

== vs === (동등 vs 일치)

=== (일치 연산자)

타입 변환 없이 타입과 값이 모두 같을 때만 true입니다.

JS
5 === 5      // true
5 === '5'    // false — 타입이 다름
null === undefined // false

== (동등 연산자)

타입이 다르면 ** 암묵적 형변환을 거친 뒤** 비교합니다. 변환 규칙이 복잡합니다.

JS
5 == '5'     // true — '5' → 5로 변환
null == undefined // true — 특수 규칙
0 == false   // true — false → 0
'' == false  // true — '' → 0, false → 0

==의 동작 알고리즘 (Abstract Equality Comparison)

ECMAScript 스펙에 정의된 핵심 규칙들을 정리하면 이렇습니다.

  1. 타입이 같으면 ===와 동일하게 비교
  2. null == undefinedtrue
  3. 숫자와 문자열 → 문자열을 숫자로 변환
  4. 불리언이 있으면 → 숫자로 변환 (true → 1, false → 0)
  5. 객체와 원시 타입 → 객체의 valueOf()/toString() 호출

악명 높은 예제들

JS
[] == false   // true
// [] → '' → 0, false → 0 → 0 == 0 → true

[] == ![]     // true (!)
// ![] → false (빈 배열은 truthy이므로 !truthy → false)
// [] == false → 위와 동일 → true

'' == 0       // true
// '' → 0

'0' == false  // true
// false → 0, '0' → 0

' \t\n' == 0  // true
// 공백 문자열 → 0

이런 결과들이 나오기 때문에 ** 항상 ===를 사용 **하는 것이 좋습니다. ==null 체크에서만 의도적으로 쓰는 경우가 있습니다.

JS
// null 또는 undefined 체크 — 유일하게 == null이 유용한 경우
if (value == null) {
  // value === null || value === undefined
}

명시적 형변환

암묵적 형변환에 의존하지 말고, 명시적으로 변환하는 것이 좋습니다.

JS
// 문자열로
String(123)     // '123'
(123).toString() // '123'

// 숫자로
Number('123')   // 123
parseInt('123') // 123
+'123'          // 123 — 단항 + 연산자

// 불리언으로
Boolean(1)      // true
!!1             // true — 이중 부정

parseInt vs Number

JS
Number('123abc')    // NaN — 전체가 숫자가 아니면 NaN
parseInt('123abc')  // 123 — 앞에서부터 파싱

Number('')          // 0
parseInt('')        // NaN

Number('0x1A')      // 26
parseInt('0x1A', 16) // 26

Symbol

Symbol은 ES2015에서 추가된 원시 타입으로, ** 유일한(unique) 값 **을 만듭니다.

JS
const s1 = Symbol('id');
const s2 = Symbol('id');
console.log(s1 === s2); // false — 설명이 같아도 다른 값

주요 용도

객체의 고유한 프로퍼티 키로 사용하여 이름 충돌을 방지합니다.

JS
const ID = Symbol('id');
const user = {
  [ID]: 1234,
  name: 'Alice'
};

console.log(user[ID]);         // 1234
console.log(Object.keys(user)); // ['name'] — Symbol은 열거되지 않음

Well-Known Symbols

자바스크립트 엔진이 내부적으로 사용하는 심볼들도 있습니다.

JS
// Symbol.iterator — 이터러블 프로토콜
const iterable = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        return { value: i++, done: i > 3 };
      }
    };
  }
};

for (const v of iterable) {
  console.log(v); // 0, 1, 2
}

BigInt

BigInt는 ES2020에서 추가된 원시 타입으로, ** 임의 정밀도 정수 **를 다룹니다.

JS
// Number의 한계
console.log(9007199254740992 === 9007199254740993); // true — 정밀도 손실!

// BigInt로 해결
console.log(9007199254740992n === 9007199254740993n); // false — 정확!

주의사항

JS
// Number와 혼합 연산 불가
1n + 1;        // TypeError

// 명시적 변환 필요
1n + BigInt(1); // 2n
Number(1n) + 1; // 2

// 비교는 가능
1n == 1;  // true (==는 형변환)
1n === 1; // false (타입이 다름)

주의할 점

typeof null === 'object' 함정

typeof로 null 체크를 하려고 typeof value === 'null'이라고 쓰면 항상 false입니다. null 체크에는 반드시 value === null을 사용해야 합니다.

==의 암묵적 형변환으로 인한 의도치 않은 비교

==를 사용하면 '' == 0true, '0' == falsetrue가 됩니다. 조건문에서 이런 비교가 의도치 않게 통과하면 디버깅이 어려운 버그가 됩니다. ==null 체크(value == null)에서만 의도적으로 사용하고, 나머지는 ===를 기본으로 사용하는 것이 안전합니다.

BigInt와 Number 혼합 연산

1n + 1은 TypeError를 던집니다. 두 타입 간 연산에는 반드시 명시적 변환(BigInt(1) 또는 Number(1n))이 필요합니다. 비교 연산(==, <, >)은 혼합 가능하지만, ===는 타입이 다르므로 항상 false입니다.

정리

항목설명
원시 타입string, number, boolean, undefined, null, symbol, bigint 7종
typeof null'object' (역사적 버그)
== vs =====는 암묵적 형변환 후 비교, ===는 타입+값 일치 비교
falsy 값false, 0, -0, 0n, '', null, undefined, NaN (8개)
Symbol 용도유일한 프로퍼티 키 생성, Well-Known Symbols
BigInt 주의Number와 혼합 연산 불가, 명시적 변환 필요
댓글 로딩 중...