[JavaScript] ==와 ===의 차이점
멘토링 과정 시, ==와 ===의 차이점에 대한 질문이 나왔다. 단순하게 타입 차이(?)라고만 알고 있었던 터라 자신있게 대답하지 못했다.
이 차이점에 대해 한번 꼭 공부하고 넘어가야지 하던 중, 인사이드 자바스크립트 3장을 읽다가 동등 연산자(==)와 일치 연산자(===)의 차이에 대해 알게 되었다.
ECMAScript 명세서를 참고해 정확하게 짚고 넘어가자.
==와 ===
먼저 ==는 동등 연산자(coercive equality)이다. 비교 시 피연산자의 타입이 다를 경우 강제 변환을 허용한다. 따라서 비교하려는 피연산자의 타입이 다를 경우, 우선 타입 변환을 거친 후 비교한다.
===는 엄격 동등 연산자(strict equality)로써, 비교 시 피연산자의 타입이 다를 경우 강제 변환을 허용하지 않는다. 즉 비교하려는 피연산자의 타입이 달라도 타입을 변경하지 않고 비교한다.
console.log(1 == "1"); // true
console.log(1 === "1"); // false
동등 연산자의 경우, 두 피연산자의 타입이 다르므로 같은 타입으로 (숫자) 변환해서 두 값이 같다고 판단했다. 엄격 동등 연산자의 경우, 타입 변환을 거치지 않아 두 값이 같지 않다고 판단한다.
따라서 동등 연산자를 사용했을 경우에 타입 변환에 대한 잘못된 결과를 얻을 수 있으므로 대부분의 자바스크립트 코딩 가이드에서는 == 연산자로 비교하는 것을 추천하지 않는다.
1. IsLooselyEqual
== 연산자에서 비교를 위해 어떤 알고리즘을 따를까
ECMAScript
명세서를 참고해보면 해당 알고리즘을 따른다.
- x와 y의 타입이 동일하면 === 연산자 비교 알고리즘 (
isStrictlyEqual
)을 사용한다. - x와 y가 각각
null
,undefined
면 true를 반환한다. - 문자와 숫자를 비교할 시,
ToNumber
을 통해 타입을 숫자로 강제 변환한 후 비교한다. - 문자와
BigInt
를 비교할 시,StringToBigInt
를 통해 문자를BigInt
로 변환한 후 비교한다. (7) - x 또는 y가
boolean
이라면 이를ToNumber
을 통해 타입을 숫자로 강제 변환한 후 비교한다. - x 또는 y가 원시 자료형(
String, Number, BigInt
)이고, 다른 피연산자가 참조 자료형인Object
일 경우,ToPrimitive
를 통해Object
를 원시 자료형으로 강제 변환한 후 비교한다. - x 또는 y가
BigInt
고 다른 피연산자가Number
일 경우, 두 가지 조건(13-a, 13-b)에 따라 값을 반환한다. - 위에 해당되는 조건에 의해
true
값이 반환되지 않을 경우,false
를 반환한다.
https://tc39.es/ecma262/#sec-islooselyequal
2. IsStrictlyEqual
그렇다면 === 연산자는 비교를 위해 어떤 알고리즘을 따를까
ECMAScript
명세서를 참고해보면 해당 알고리즘을 따른다.
- x, y의 타입이 다를 경우 false를 반환한다.
- x가
Number
일 경우,Number::equal
을 통해 값을 반환한다. SameValueNonNumber
을 통해 값을 비교하고 반환한다.
https://tc39.es/ecma262/#sec-isstrictlyequal
암묵적 타입 변환
타입이 강제로 변환되는 것은 암묵적 타입 변환(Implicit coercion)이라고도 한다.
이는 동등 연산자를 사용했을 때 뿐만 아닌, JS 엔진에 의해 암묵적으로 자동 변환되는 것을 말한다.
let x = 100;
let str = x + "";
console.log(typeof str); // string
console.log(x); // 100 (변수 x의 값은 변경되지 않는다.)
암묵적 타입 변환이 기존 값을 직접 변경하는 것이 아니다. JS 엔진이 표현식을 에러없이 평가하기 위해 기존 값을 바탕으로 새로운 타입의 값을 만들어 단 한번만 사용하고 버리게 된다.
위의 예제를 보면, x의 숫자 값을 바탕으로 새로운 문자열 ‘100’을 생성하고 이것으로 표현식 ‘100’ + ‘ ’을 평가한다. 이때 자동으로 생성된 문자열 ‘100’은 표현식의 평가가 끝나면 아무도 참조하지 않으므로 가비지 컬렉터에 의해 메모리에서 제거된다.
1. Context
JS 엔진은 표현식을 평가할 때 Context
를 고려해 암묵적 타입 변환을 실행한다.
// 문자열 타입으로 변환
"10" + 5; // '105'
// 숫자 타입으로 변환
5 * "10"; // 50
5 - "10"; // -5
5 / "hello"; // NaN
!0; // true
1-1. 문자열 타입으로 변환
‘+’ 연산자는 피연산자 중 하나 이상이 문자열이므로 문자열 연결 연산자로 동작한다. (모든 피연산자가 숫자일 경우에는 더하기 연산자로 동작한다.)
문자열 연결 연산자는, 문자열 값을 만든다. 따라서 문자열 연결 연산자의 피연산자는 Context
상 문자열 타입이어야 한다.
JS은 따라서 문자열 연결 연산자의 피연산자 중에서 문자열 타입이 아닌 피연산자를 문자열 타입으로 암묵적 타입 변환한다.
1-2. 숫자 타입으로 변환
산술 연산자를 사용하는 경우에는, 숫자 값을 만들어야 하므로 Context
상 숫자 타입으로 암묵적 타입 변환하게 된다. 다만 피연산자를 숫자 타입으로 변환할 수 없는 경우에는 산술 연산을 수행할 수 없으므로 NaN
을 반환한다.
1-3. Boolean 타입으로 변환
if문과 for문 제어문의 조건식은 Boolean
값을 반환해야 하는 표현식이다. 따라서 JS 엔진은 조건식의 평가 결과를 Boolean
타입으로 암묵적 타입 변환한다.
이때 JS 엔진은 불리언 타입이 아닌 값을 Truthy
값(참으로 인식할 값) 또는 Falsy
(거짓으로 인식할 값)으로 구분한다. 즉 Truthy
값은 true로, Falsy
값은 false로 변환된다.
아래 값들은 제어문의 조건식과 같이 불리언 값으로 평가되어야 할 컨텍스트에서 false로 평가되는 Falsy
값이다.
- false
- undefined
- null
- 0, -0
- NaN
- ’’ (빈문자열)
명시적 타입 변환
명시적 타입 변환(Explicit coercion)은 개발자의 의도에 의해 타입을 변경하는 것이다.
해당 변환을 위한 방법은 다양하다. 래퍼 객체를 생성하기 위해 사용하는 래퍼 객체 생성자 함수를 new 연산자 없이 호출하는 방법과 자바스크립트에서 제공하는 빌트인 메소드를 사용하는 방법, 그리고 앞에서 살펴본 암묵적 타입 변환을 이용하는 방법이 있다.
출처
ECMAScript_2023_Language_Specification
댓글남기기