느슨한(==)/엄격한(===) 동등 비교
많은 서적이나 블로그에서 보면 대부분 "==는 값의 동등함을, ===는 값과 타입 모두의 동등함을 비교한다" 는 글을 본 적이 있을 것이다.
이는 그럴 듯한 내용이지만 사실 정확하진 않다.
정정하자면,
"동등함의 비교시 ==는 강제변환을 허용하지만, ===는 강제변환을 허용하지 않는다."
엄격한(===) 비교는 느슨한(==) 비교에 비해 타입까지 비교해야하니 당연히 일이 더 많다.
그러나, 마치 느슨한(==) 비교가 엄격한(===) 비교보다 눈에 띄게 처리가 더뎌서 성능에 영향을 끼친다고 생각하지 말자.
불과 몇 마이크로 초 단위(1 마이크로 초 = 100만분의 1초)의 차이일뿐 이다.
게다가 타입이 같은 두값의 동등 비교라면 ==와 ===의 알고리즘은 동일하다.
엔진의 내부 구현 방식은 조금씩 다를 수도 있지만, 기본적으로 하는 일은 같다.
여기 비교과정에서 살펴봐야할 부분은 강제변환의 개입 여부를 봐야한다.
강제변환이 필요하다면 느슨한 동등 연산자(===)를, 필요하지 않다면 엄격한 동등 연산자(===)를 사용하자.
문자열, String ► 숫자, Number
1 ) Type(a)가 Number 고, Type(b)가 String이면, a == ToNumber(b) 비교 결과를 반환한다.
2 ) Type(a)가 String 이고, Type(b)가 Number이면, ToNumber(a) == b 비교 결과를 반환한다.
var a = 59;
var b = "59";
a === b; // false
a == b; // true
* ► 불리언, Boolean
1 ) Type(a)가 *(모든 객체) 고, Type(b)가 Boolean이면, a == ToNumber(b) 비교 결과를 반환한다.
2 ) Type(a)가 Boolean 이고, Type(b)가 *(모든 객체)면 ,ToNumber(a) == b 비교 결과를 반환한다.
즉, ==의 피연산자 한쪽이 불리언 값이면 예외없이 그 값이 먼저 숫자로 강제변환된다.
var a = 59;
var b = 1;
var c = 0;
var d = true;
var e = false;
a == d; // false
b == d; // true
c == e; // true
// 비권장
if (a == true) {}
// 권장 - 암시적 강제변환
if (a) {}
// 권장 - 명시적 강제변환
if (!!a) {}
if (Boolean(a)) {}
null ► undefined
1 ) a가 null 이고, b가 undefined면 true를 반환한다.
2 ) a가 undefined 이고, b가 null이면 true를 반환한다.
즉, null과 undefined는 느슨한 동등 비교시 상호간의 암시적인 강제변환이 일어나므로 비교 관점에서 구분이 되지 않는 값으로 취급된다.
var a = null;
var b;
a == b; // true
a == null; // true
b == null; // true
var c = doSomething();
// 비권장
if ( c === undefined || c === null ) {}
// 권장
if ( c == null ) {}
객체, Object(함수/배열) ► 비객체(원시값, Boolean 제외)
1 ) Type(a) 가 String 또는 Number 이고, Type(b) 가 Object 라면, a == ToPrimitive(b)의 비교 결과를 반환한다.
2 ) Type(b) 가 Object이고, Type(a)가 String 또는 Number 라면, ToPrimitive(a) == b의 비교 결과를 반환한다.
예외 1) null과 undefined은 객체 래퍼가 없어 Object로 박싱될 수 없어 동등 비교를 할 수 없다.
예외 2) NaN은 Number 객체 래퍼로 박싱되지만, NaN은 자기자신과도 같지 않으므로 동등 비교를 할 수 없다.
var a = 59;
var b = [59];
a == b; // true
var a = "test";
var b = Object(a);
a == b; // true
a === b; // false
var a = null;
var b = Object(a);
a == b; // false
var a = undefined;
var b = Object(a);
a == b; // false
var a = NaN;
var b = Object(a);
a == b; // false
★ 특이사항 ★
falsy 값 비교에 관해 아래의 긍정 오류 사례를 보면, "" 와 0은 같은 값이 아님에도 동등 비교시 true로 나오고 있기 때문에 주의가 필요하다.
"0" == false; // true
0 == false; // true
"" == false; // true
[] == false; // true
"" == 0; // true
"" == []; // true
0 == []; // true
또 아래의 사례도 참고해서 한번쯤 생각해보자..
어떠한 강제변환/추상연산으로 인해 왜 이런 결과가 나왔을지 알아보자.
[] == ![]; // true
2 == [2]; // true
"" == [null]; // true
0 == "\n"; // true
( 1 ) [] == ![] : 우측의 단항 연산자(!)로 인해 ToBoolean으로 명시적 강제변환을 하여 [] == false가 되었고, 긍정오류로 인해 true 반환
( 2 ) 2 == [2] : 우측의 비객체 값이 ToPrimitive로 ToNumber가 되어 숫자 2가 되어 true 반환
( 3 ) "" == [null] : 우측의 비객체 값이 ToPrimitive로 ToString이 되었으나 null로 인해 ""이 되어 true 반환
( 4 ) 0 == "\n" : 우측의 "\n"(또는 " ", 공백문자)가 ToNumber로 0으로 강제 변환되어 true 반환
따라서,
피연산자 중 하나가 true/false일 가능성이 있으면 '절대로' == 연산자를 쓰지말자.
피연산자 중 하나가 [], "", 0이 될 가능서이 있으면 '가급적' == 연산자를 쓰지말자.
위와 같은 상황이라면 == 대신 ===를 사용하여 의도하지 않은 강제변환을 차단하는게 훨씬 좋다.
추상 관계 비교
추상적 관계 비교(<,>,<=,>=) 알고리즘은 비교시 피연산자 모두 문자열일때와 그 외의 경우 두가지로 나뉜다.
( 1 ) 피연산자 모두 문자열인 경우, 문자 단위로 비교한다.
var a = ["59"];
var b = ["060"];
a < b; // false
// a의 "5"와 b의 "0"을 비교해 어휘상 "5"가 "0"보다 크므로 false
( 2 ) 피연산자가 배열인 경우, ToString로 강제변환하여 문자열로 만들어 문자 단위로 비교한다.
var a = [5, 9];
var b = [0, 6, 0];
a < b; // false
// a는 "5, 9", b는 "0, 6, 0"으로 문자열화한 후 문자 단위로 어휘 비교
( 3 ) 피연산자 한쪽이라도 문자열이 아닐 경우, 양쪽 모두 ToNumber로 강제변환하여 숫자값으로 만들어 비교한다.
var a = [59];
var b = ["60"];
a < b; // true
b < a; // false
( 4 ) 피연산자가 객체 인 경우, Object로 변환되어 어휘적인 비교가 하여 false이다.
단, 그 반대의 경우에는 true일 수 있다.
a <= b 는 실제로 a > b 의 평가 결과를 부정하도록 명세에 기술되어 있고,
더 나아가 a >= b는 a <= b로 재해석한 다음 동일한 추론을 적용한다.
자바스크립트 엔진은 <=를 '더 크지 않은'(!(a > b) → !(b < a)로 처리) 의 의미로 해석하고 있다.
var a = {c : 59};
var b = {c : 60};
a < b; // false
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
불행히도 동등 비교에 관한한 '엄격한 관계 비교'는 없다.
즉, 추상적 관계 비교 전 a 와 b 모두 명시적으로 동일한 타입임을 확실히하는 방법 외에는
a < b 같은 관계 비교 과정에서 암시적 강제변환을 원천 봉쇄할 수 없다.
[강제변환 - 명시적 강제변환, Explicit Coercion] chati.tistory.com/153
[강제변환 - 암시적 강제변환, Implicit Coercion] chati.tistory.com/154
[참고] You Don't Know JS 타입과 문법, 스코프와 클로저 - 카일 심슨
'Javascript' 카테고리의 다른 글
[JavaScript] 렉시컬 스코프, Lexical Scope / 동적 스코프, Dynamic Scope (0) | 2020.09.22 |
---|---|
[JavaScript] 스코프, Scope (0) | 2020.09.21 |
[JavaScript] 연산자 우선순위 (0) | 2020.09.21 |
[JavaScript] 강제변환 - 암시적 강제변환, Implicit Coercion (0) | 2020.09.16 |
[JavaScript] 강제변환 - 명시적 강제변환, Explicit Coercion (0) | 2020.09.16 |
[JavaScript] 강제변환 - 추상 연산, Abstract operation (0) | 2020.09.16 |