"같다"를 묻는 두 가지 방법
자바스크립트에는 동등 연산자가 두 개 있습니다. 엄격 비교 ===와 느슨한 비교 ==죠. 생김새는 거의 똑같지만, 동작은 완전히 다릅니다.
===는 양쪽 피연산자의 타입과 값이 모두 같은지 확인합니다. 반면 ==는 먼저 공통 타입으로 변환한 뒤 비교하죠. 바로 이 변환 과정, 즉 **타입 강제 변환(type coercion)**이 자바스크립트의 동등 비교가 악명을 얻게 된 주범입니다.
짧게 요약하면, 기본적으로는 ===를 쓰세요. 하지만 왜 ==를 피하는지 그 이유는 한 번쯤 제대로 알아둘 가치가 있습니다.
엄격 비교(===): 대부분의 경우 이게 정답
===는 타입과 값이 모두 일치할 때만 true를 반환합니다. 타입 변환도, 의외의 결과도 없죠:
타입이 다르면 결과는 곧바로 false입니다. 반대로 타입이 같으면 자바스크립트는 값을 비교하는데, 원시 타입(primitive)은 값 자체를 비교하고 객체는 참조(reference)를 비교합니다. 객체 비교에 대해서는 잠시 뒤에 자세히 다루겠습니다.
!==는 엄격 부등 연산자이며, 같은 규칙을 그대로 뒤집어 적용합니다.
느슨한 동등 비교: 숨겨진 타입 강제 변환
== 연산자는 양쪽의 타입이 달라도 괜찮습니다. 대신 비교하기 전에 양쪽 타입을 맞추려고 타입 강제 변환(coercion) 과정을 거칩니다.
정확한 규칙은 명세에 다 적혀 있고 뜯어보면 그렇게 끔찍한 수준은 아니지만, 디버깅 한창일 때 그걸 머릿속에서 떠올리는 건 또 다른 얘기죠. "0" == false가 true라는 사실은 경력 많은 개발자도 종종 헷갈립니다. [] == false도 마찬가지예요 (배열이 ""로 강제 변환되고, 그게 다시 0으로 변환되기 때문에 결과는 true입니다).
대부분의 스타일 가이드와 ESLint의 eqeqeq 규칙이 기본적으로 ===를 쓰라고 권하는 이유가 여기에 있습니다. 키 하나 더 치는 대신 머리로 기억할 수 있는 규칙을 얻는 거죠.
알아둘 만한 단 하나의 == 패턴
그래도 느슨한 동등 비교(==) 중에 딱 하나 기억해둘 만한 관용 패턴이 있습니다. 바로 x == null인데요, x가 null이거나 undefined일 때 true를 반환하고 그 외에는 모두 false를 반환합니다.
엄격하게 쓰자면 x === null || x === undefined가 되는데, 솔직히 좀 번잡해 보입니다. 그래서 많은 프로젝트에서 == null만큼은 유일한 예외로 허용하곤 합니다. 어느 쪽이든 규칙을 하나 정했으면 일관되게 지키는 게 중요합니다.
객체는 참조로 비교된다
객체, 배열, 함수를 비교할 때는 ===든 ==든 던지는 질문이 똑같습니다. "양쪽이 메모리상에서 같은 객체를 가리키고 있는가?" 라는 질문이지, "내용물이 같은가?"가 아닙니다.
내용이 똑같아 보이는 객체 리터럴 두 개도 자바스크립트에서는 엄연히 다른 객체입니다. 객체 비교를 처음 다룰 때 누구나 한 번은 걸려 넘어지는 부분이죠.
값 기반으로 객체를 비교하고 싶다면 직접 헬퍼 함수를 만들거나, 라이브러리(lodash.isequal)를 쓰거나, 단순한 평범한 객체라면 JSON.stringify로 직렬화해서 비교하는 방법을 씁니다:
JSON.stringify는 단순한 데이터에만 잘 동작합니다. 함수, undefined, 심볼은 무시되고, 엔진마다 모든 형태의 객체에서 키 순서를 보장해 주지도 않죠. 빠르게 확인할 때 쓰는 꼼수일 뿐, 범용 해법은 아닙니다.
NaN은 그 무엇과도 같지 않다
NaN("not a number")은 숫자 연산이 의미 있는 답을 낼 수 없을 때 자바스크립트가 돌려주는 값입니다. 예를 들어 0/0, Number("abc"), Math.sqrt(-1) 같은 경우죠. 두 동등 연산자(==, ===) 모두 한쪽이 NaN이면 false를 반환하고, 양쪽 다 NaN이어도 결과는 false입니다:
NaN을 판별하려면 Number.isNaN(value)을 사용하세요. 예전부터 있던 전역 isNaN은 쓰지 않는 게 좋습니다. 인자를 먼저 형 변환하기 때문에 isNaN("hello")가 true가 되는데, 이런 동작을 원하는 경우는 거의 없습니다.
Object.is: ===에 두 가지만 손본 비교
Object.is(a, b)는 기본적으로 ===와 동일하게 동작하지만, 딱 두 가지 경우에서 다르게 처리됩니다.
대부분의 경우에는 ===를 쓰면 됩니다. Object.is가 필요한 순간은 딱 두 가지예요. NaN을 자기 자신과 같다고 처리해야 할 때, 그리고 +0과 -0을 구분해야 할 때입니다. 둘 다 흔한 상황은 아니지만, 수치 계산 코드나 프레임워크 내부 구현에서는 가끔 중요하게 쓰입니다(실제로 React는 상태 비교에 Object.is를 사용합니다).
부등 연산자도 똑같이 두 갈래
!==는 엄격 비교, !=는 느슨한 비교예요. 앞서 말한 원칙이 그대로 적용됩니다:
기본은 !==입니다. 만약 == null 사용을 허용하는 스타일이라면, "null도 아니고 undefined도 아니다"를 확인하는 짝꿍인 != null도 함께 허용해도 좋습니다.
값을 비교할 때 체크리스트
두 값을 비교해야 할 때는 아래 순서대로 따져보세요.
- 같은 타입의 원시값(primitive) 비교인가? →
===를 씁니다. - null이나 undefined 체크인가? → 스타일 가이드가 허용한다면
x == null로 간단히, 아니라면x === null || x === undefined로 풀어 씁니다. NaN체크인가? →Number.isNaN(x)를 씁니다.- 객체를 참조(identity) 기준으로 비교하는가? →
===가 정확히 그 역할을 합니다. - 객체를 내용(contents) 기준으로 비교하는가? → 직접 헬퍼 함수를 만들거나, 라이브러리를 쓰거나, 직렬화(serialize)해서 비교해야 합니다. 기본 연산자로는 해결되지 않습니다.
평소에는 ===만 고수하고, ==는 == null 패턴에서만 제한적으로 꺼내 쓴다는 원칙을 지키면, 자바스크립트 FAQ를 가득 채우는 동등 비교의 함정들을 대부분 피할 수 있습니다.
다음 주제: 연산자
동등 비교는 자바스크립트 연산자의 일부일 뿐입니다. 다음 문서에서는 산술 연산자, 논리 연산자, 할당 연산자, 그리고 실무에서 자주 쓰는 단축 연산자까지 나머지 연산자들을 차례로 살펴봅니다.
자주 묻는 질문
자바스크립트에서 == 와 === 는 어떻게 다른가요?
===는 엄격한 동등 비교(strict equality)로, 타입과 값이 모두 같아야 true를 반환합니다. 반면 ==는 느슨한 동등 비교(loose equality)라서, 비교 전에 양쪽 피연산자를 같은 타입으로 강제 변환합니다. 예를 들어 1 === '1'은 false지만, 1 == '1'은 문자열을 숫자로 변환하기 때문에 true가 됩니다.
자바스크립트에서는 무조건 === 만 쓰는 게 좋나요?
기본적으로는 ===를 쓰는 게 좋습니다. 규칙이 단순해서 머릿속으로 예측하기 쉽거든요. 유일하게 자주 쓰이는 예외가 x == null 패턴인데, 이렇게 쓰면 null과 undefined를 한 번에 걸러낼 수 있습니다. ESLint의 eqeqeq 룰도 보통 ===를 강제하면서 이 패턴만 옵션으로 허용합니다.
왜 NaN === NaN 이 false인가요?
IEEE 754 표준에서 NaN은 자기 자신을 포함해 그 어떤 값과도 같지 않다고 정의되어 있습니다. 그래서 NaN이 낀 동등 비교는 무조건 false가 나와요. 어떤 값이 NaN인지 확인하려면 Number.isNaN(x)를 쓰거나, Object.is(NaN, NaN)을 쓰면 됩니다. Object.is는 이 경우 true를 돌려줍니다.
객체 두 개가 같은지 어떻게 비교하나요?
==든 ===든 객체는 내용이 아닌 참조(reference)로 비교합니다. 그래서 {a: 1} === {a: 1}은 서로 다른 객체이기 때문에 false가 나와요. 내용까지 같은지 보려면 직접 비교 로직을 짜거나, Lodash의 isEqual 같은 라이브러리를 쓰거나, 단순한 plain object라면 JSON.stringify로 직렬화해서 비교하는 방법도 있습니다.