본문 바로가기

Javascript

[JavaScript] 느슨한(==)/엄격한(===) 동등 비교, 추상 관계 비교

반응형

 

느슨한(==)/엄격한(===) 동등 비교


많은 서적이나 블로그에서 보면 대부분 "==는 값의 동등함을, ===는 값과 타입 모두의 동등함을 비교한다" 는 글을 본 적이 있을 것이다.

이는 그럴 듯한 내용이지만 사실 정확하진 않다.

 

정정하자면,

 "동등함의 비교시 ==는 강제변환을 허용하지만, ===는 강제변환을 허용하지 않는다." 

 

엄격한(===) 비교는 느슨한(==) 비교에 비해 타입까지 비교해야하니 당연히 일이 더 많다.

그러나, 마치 느슨한(==) 비교가 엄격한(===) 비교보다 눈에 띄게 처리가 더뎌서 성능에 영향을 끼친다고 생각하지 말자.

불과 몇 마이크로 초 단위(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

 

[JavaScript] 강제변환 - 명시적 강제변환, Explicit Coercion

이 글의 내용은 자바스크립트의 강제변환의 좋고 나쁨을 충분히 이해하고, 자신의 프로그램에 적절한지 스스로 현명하게 판단할 수 있도록 하기위한 내용을 작성했다. 어떤 값을 다른 타입의 ��

chati.tistory.com

 

[강제변환 - 암시적 강제변환, Implicit Coercion] chati.tistory.com/154

 

[JavaScript] 강제변환 - 암시적 강제변환, Implicit Coercion

이 글의 내용은 자바스크립트의 강제변환의 좋고 나쁨을 충분히 이해하고, 자신의 프로그램에 적절한지 스스로 현명하게 판단할 수 있도록 하기위한 내용을 작성했다. 어떤 값을 다른 타입의 ��

chati.tistory.com

 


[참고] You Don't Know JS 타입과 문법, 스코프와 클로저 - 카일 심슨

 

반응형

❥ CHATI Github