1. 개요

==와 ===의 차이를 “타입까지 검사하느냐”로 외우고 넘어가는 경우가 많다. 맞는 말이지만 그것만으로는 왜 ==이 위험한지 감이 안 온다. ==은 타입이 다르면 암묵적으로 형변환을 한 뒤 비교한다. 그 형변환 규칙이 직관과 어긋나는 경우가 꽤 있다. 몇 가지만 알아도 왜 팀 컨벤션에서 ===을 강제하는지 이해가 된다.


2. 핵심 내용

2-1. == 은 타입이 다르면 변환부터 한다

1 == "1";           // true, 문자열을 숫자로 변환
0 == false;         // true, boolean 을 숫자로 변환
null == undefined;  // true, 이 둘만 예외적으로 서로 같다고 취급

규칙 자체는 정해져 있지만(ECMA-262의 추상 동등 비교 알고리즘), 외우기엔 항목이 많고 직관과 어긋나는 조합이 섞여 있다.

2-2. 흔히 틀리는 조합들

"" == 0;        // true, 빈 문자열이 숫자로 변환되면 0
"" == false;    // true
[] == false;    // true, 배열이 문자열을 거쳐 숫자로 이중 변환된다
[] == ![];      // true, ![] 는 false, 그리고 [] == false
"0" == false;   // true
null == 0;      // false, null 은 undefined 하고만 같다고 취급된다
NaN == NaN;     // false, NaN 은 자기 자신과도 같지 않다

[] == ![]가 true인 게 대표적인 예다. 배열을 논리 부정(!)하면 false가 되고, 그다음 [] == false에서 배열이 다시 빈 문자열로, 빈 문자열이 다시 0으로 변환돼 false(0)와 같아진다. 이런 걸 실무에서 의도하고 쓰는 사람은 없다.

2-3. === 은 변환을 안 한다

1 === "1";           // false, 타입이 다르면 그냥 false
0 === false;         // false
null === undefined;  // false

타입이 다르면 무조건 false다. 예측 가능하다. 팀 컨벤션에서 ==을 금지하고 ===을 강제하는 이유가 이거다. 형변환 규칙을 매번 떠올리며 코드를 읽을 필요가 없어진다.

TypeScript에서 typeof x === "string"처럼 항상 ===으로 타입을 좁히는 것도 같은 이유다. 여기서 ==을 썼다간 형변환이 끼어들어 좁혀지는 타입 자체가 흔들린다. 이 좁히기(narrowing) 얘기는 TypeScript 기초 2 - 유니온과 타입 좁히기에서 이어간다.

2-4. NaN 과 -0, 그리고 Object.is

===도 만능은 아니다. NaN === NaN은 false고, +0 === -0은 true다. 값 비교가 정말 엄밀해야 할 때(캐시 키 비교 등)는 Object.is를 쓴다.

Object.is(NaN, NaN);  // true
Object.is(+0, -0);    // false
[NaN].includes(NaN);  // true, includes 는 내부적으로 SameValueZero 사용
[NaN].indexOf(NaN);   // -1, indexOf 는 === 사용

같은 “NaN을 찾는다”인데 includes와 indexOf가 다른 결과를 낸다는 걸 모르면 디버깅에서 시간을 꽤 쓴다.

2-5. 예외적으로 ==을 허용하는 한 곳

value == null은 value가 null이거나 undefined일 때만 true다. 두 값을 한 번에 걸러야 할 때 의도적으로 쓰는 관용구다.

if (value == null) { /* null 또는 undefined */ }
// 위 한 줄이 아래와 같다
if (value === null || value === undefined) { /* ... */ }

이 경우를 빼면 ==을 쓸 이유는 거의 없다.


3. 마무리

요약

  • ==은 타입이 다르면 암묵적 형변환 후 비교한다, 규칙이 많고 직관과 어긋난다
  • ===은 타입이 다르면 무조건 false다, 예측 가능하다
  • NaN === NaN은 false, 엄밀 비교가 필요하면 Object.is
  • value == null은 null/undefined 를 한 번에 거르는 유일하게 허용되는 관용구다

다음은 CommonJS 와 ESM 이다.