다른 언어와는 달리 JS에는 ==와 === 이라는 두 가지의 동등 비교 연산자가 존재한다.
이 두 연산자의 차이점을 설명하라고 하면 대부분 이렇게 답할 것이다.
== 연산자는 값이 같을 경우 참을 반환하고 === 연산자는 값과 타입이 모두 같을 경우 참을 반환한다.
나도 지금까지 이렇게 알고 있었고 설명하라고 하면 이렇게 설명해왔다.
하지만 어제 읽은 책에서는 이 정의가 틀렸다고 했다.
정확히 말하면, "동등함의 비교 시 ==는 강제변환을 허용하지만, ===는 강제변환을 허용하지 않는다." 가 정답이라고 한다.
== 연산자의 로직은 ES5 11.9.3 The Abstract Equality Comparison Algorithm에 적혀있다.
== 연산자는 강제변환을 허용하기 때문에 가능한 피연산자의 조합 별로 어떻게 강제변환이 수행되는지 길게 나열되어 있다.

먼저, 비교할 두 피연산자가 같은 타입이면 값을 비교한다.
console.log(undefined == undefined); // true
console.log(null == null); // true
console.log(13 == 13); // true
console.log(NaN == NaN); // false
console.log(+0 == -0); // true
console.log("asd" == "asd"); // true
console.log(true == true); // true
다만 NaN은 NaN과도 동등하지 않기 때문에 두 피연산자 중 NaN이 있다면 무조건 false이다.
또 주의할 점은 두 피연산자가 각각 +0, -0의 값을 가질 경우 결과는 true라는 것이다.
두 피연산자가 각각 null, undefined의 값을 가질 경우 두 피연산자의 느슨한 동등 비교 결과는 항상 true이다.
console.log(null == undefined); // true
console.log(undefined == null); // true
두 피연산자가 하나는 숫자, 하나는 문자열일 경우 ToNumber 추상 연산 로직에 의해 문자열이 숫자로 강제변환된다.
"13" == 13
→ 13 == 13 // 1. 문자열 "13"이 숫자 13으로 강제변환된다.
→ true
두 피연산자 중에서 한 피연산자가 불리언일 경우 ToNumber 추상 연산 로직에 의해 불리언이 숫자로 강제변환된다.
즉, true일 때는 1로, false일 때는 0으로 강제변환되는 것이다.
"13" == true
→ "13" == 1 // 1. 불리언 true가 숫자 1로 강제변환된다.
→ 13 == 1 // 2. 문자열 "13"이 숫자 13으로 강제변환된다.
→ false
두 피연산자 중 하나는 객체(객체, 함수, 배열)이고 다른 하나는 비객체(문자열, 숫자, 불리언 등의 스칼라 원시 값)일 경우 ToPrimitive 추상 연산 로직에 의해 객체가 스칼라 원시 값으로 강제변환된다.
[13] == 13
→ "13" == 13 // 1. 배열 [13]이 문자열 "13"으로 강제변환된다.
→ 13 == 13 // 2. 문자열 "13"이 숫자 13으로 강제변환된다.
→ true
두 피연산자가 모두 객체일 경우 두 피연산자가 동일한 객체를 참조할 때에만 동등하다.
즉, 여기서는 강제변환이 일어나지 않는다.
const a = b = ["13"];
console.log(a == b); // true
console.log(["13"] == ["13"]); // false
이렇게 == 연산자가 강제변환을 하는 방법을 외운다고 해도 순간 멈칫하게 되는 사례들이 존재한다.
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
따라서 헷갈림을 방지하기 위해 다음의 경우에는 엄격한 동등비교(===)를 권장한다.
추상 관계 비교 연산자(<,> 등)도 강제변환을 통해 값을 계산한다.
< 연산자에 대한 로직은 ES5 11.8.5 The Abstract Relational Comparison Algorithm에 적혀있다.
참고로 > 연산자는 a > b라고 했을 때 b < a와 같이 처리되기 때문에 < 연산자에 대해서만 정의되어 있다.
추상 관계 비교 연산자는 두 피연산자에 대해 ToPrimitive 강제변환을 실시한다.
따라서 두 피연산자 모두 문자열이 아닐 경우 ToNumber 추상 연산 로직에 의해 양쪽 모두 숫자로 강제변환된다.
13 < [14]
→ 13 < 14
→ true
피연산자가 둘 다 문자열일 경우 각 문자를 알파벳 순서로 비교한다.
[1,3] < [1,4]
→ "1,3" < "1,4"
→ true
<= 연산자나 >= 연산자에 대해 설명하라고 하면 "작거나 같은", "크거나 같은"으로 설명할 수 있다.
<= 연산자도 < 연산자와 동일하게 강제변환을 통해 계산되며 동일한 값일 경우 true를 반환하는 로직만 추가된다.
하지만 조심해야 할 것은 자바스크립트 엔진이 <= 연산자를 해석할 때 "작거나 같은"이 아닌 "더 크지 않은"의 의미로 해석한다는 것이다.
즉, a <= b → !(a > b) → !(b < a)로 처리된다.
13 <= [13]
→ !(13 > [13])
→ !([13] < 13)
→ !("13" < 13)
→ !(13 < 13)
→ !(false)
→ true