[JS] ==와 ===의 차이점, 헷갈리는 비교 연산 예제들

Wol-dan·2021년 9월 19일
0

자바스크립트

목록 보기
5/6
post-thumbnail

=====

자바스크립트에서 일치하는지(같은지) 비교하는 연산자(equality operator)에는 대표적으로 두 가지가 있다. =====이다.

흔히 ==를 느슨한 동등 비교 연산자(loose equality comparison), ===엄격한 동등 비교 연산자(strict equality comparison)라고 한다.

🤔 =====의 차이점

==(loose equality comparison, double equals)

  • 두 변수를 비교할 때 타입이 서로 다르면 타입 변환을 수행한다. (타입이 같으면 타입 변환을 하지 않는다.)
  • NaN, +0, -0에 대한 특별한 처리를 한다.
+0 == -0; // true
NaN == NaN; // false

===(strict equality comparison, triple equals)

  • 비교할 때 타입 변환이 일어나지 않는다.
  • 그러니까, 두 변수의 타입이 다르면 같지 않은 것으로 보고 false를 리턴한다.
  • 두 변수의 타입이 같고, 같은 값을 가질 때, true를 반환한다. 비교하려는 값이 숫자일 경우 둘 다 NaN이 아니어야하고, 값이 같아야 같은 것으로 본다. +0-0을 비교하는 경우도 같다고 보고 true를 리턴한다.
  • NaN, +0, -0에 대한 특별한 처리를 한다.
+0 === -0; // true
NaN === NaN; // false

Object.is

  • 비교할 때 타입 변환이 일어나지 않는다.
  • NaN, +0, -0을 다루지 않는다. 이 점만 빼면 ===와 동작방식이 똑같다.
Object.is(NaN, NaN); // true
Object.is(+0, -0); // false

🤔 지금까지 설명한 것은 비교하려는 두 변수가 원시 타입(Primitive Type)일 때 얘기이다. 원시 타입이 아닌 두 객체를 비교할 경우 구조가 똑같다하더라도 같지 않으므로 false를 리턴한다. 참조타입에서는 ==를 쓰던 ===를 쓰던 동일하게 동작한다는 것이다.

📂 객체를 비교할 때

객체를 비교할 때는 다음과 같은 연산이 수행된다.

    1. 객체에 valueOf() 메서드가 있다면 호출된다.
    1. valueOf()의 결과가 원시 타입이 아니라면 toString()을 호출한 후 비교 연산을 수행한다.
let a = {};
a.toString = function () {
  return 'hello';
};
let b = 'hello';

a == b; // true;

위 예제를 보면 == 연산에서 toString()이 호출되었음을 알 수 있다. 과정을 자세히 보면 다음과 같다.

[Step 1] -> 배열 즉 객체인 a와 문자열 b를 비교하려고 한다. a는 객체이므로 a에 valueOf()연산이 수행된다.
[Step 2] -> a에 valueOf() 연산을 한 결과가 원시타입이 아니므로 toString()이 호출된다.
[Step 3] -> a의 toString()을 호출했을 때 'hello'가 반환되도록 했으므로 a와 b는 같은 것으로 판별된다.

[Result] = true

💡 Object.prototype.valueOf()

valueOf() 메서드는 특정 객체의 원시값을 반환하는 함수이다.

객체 비교 예제들

let a = {};
let b = {};

a == b; // false
a === b; // false

let c = [];
let d = [];

c == d; // false
c === d; // false

자바스크립트에서 문자열은 원시타입이지만 객체로도 만들 수 있다.

let a = 'string';
let b = new String('string');
a == b; // true
a === b; // false, 타입이 다르기 때문에 false가 리턴된다.

typeof a; // "string"
typeof b; // "object"

📅 표로 살펴보는 Equality 비교 모델

image

image

출처: 동치 비교 모델 - MDN

⌨️ 예제로 알아보기

==에서 일어나는 자동 형변환

==로 비교하려는 값의 타입이 서로 다른 경우, 자바스크립트는 이 값들을 숫자형으로 바꿔서 비교한다.

'01' == 1; // true, 문자열 '01'이 숫자 1로 변환된 후 비교가 진행된다.

// Boolean값의 경우 true는 1로, false는 0으로 변환된 후 비교가 이루어진다.
true == 1; // true
false == 0; // true

이렇게 숫자형으로 자동 변환되기 때문에 ==0false를 구별하지 못한다.

0 == false; //true, false는 숫자로 변환되어 0이 된다.
'' == false; //true, ''(빈문자열)과 false는 숫자로 변환되어 0이 된다.

타입까지 같은지 확인하는 ===을 사용하면 0false를 구별할 수 있다.

0 === false; // false, 피연산자의 타입이 다르기 때문이다.

null이나 undefined와 비교하기

null vs undefined

  • ===를 사용하여 nullundefined를 비교

두 값의 자료형이 다르기 때문에 false가 반환된다.

null === undefined; // false
  • ==를 사용하여 nullundefined를 비교

==를 사용하여 nullundefined를 비교하면 특별한 규칙이 적용돼 true가 반환된다. nullundefined 이 두 값은 자기들끼리는 잘 어울리지만 다른 값들과는 잘 어울리지 못한다.(이는 아래서 살펴볼 것이다.)

null == undefined; // true
  • 산술 연산자나 기타 비교 연산자(>,<,>=,<=)를 사용하여 nullundefined를 비교
    • nullundefined는 숫자형으로 변환된다. null0, undefinedNaN으로 변환된다.

null vs 0

null > 0; // (1) false
null == 0; // (2) false
null >= 0; // (3) true

위 코드의 비교 결과가 이상하지 않은가? (3)에서는 null0보다 크거나 같다고 했기 때문에 (1), (2) 둘 중 하나는 참이어야 하는데 둘 다 거짓을 반환하고 있다. 그 이유는 ==와 기타 비교 연산자(>,<,>=,<=)의 동작 방식이 다르기 때문이다.

  • ==로 비교할 때, 피연산자가 null이나 undefined인 경우에는 형변환을 하지 않는다.
  • 기타 비교 연산자(>,<,>=,<=)를 사용할 경우 nullundefined는 숫자형으로 반환된다. 그렇기 때문에 (1), (3)에서 null0으로 변환되는 것이다.

비교가 불가능한 undefined

undefined > 0; // (1) false
undefined < 0; // (2) false
undefined == 0; // (3) false
  • (1), (2): undefinedNaN으로 변환되는데(숫자형으로 변환) NaN이 피연산자인 경우 비교 연산자는 항상 false를 반환한다.
  • (3): undefined==로 비교했을 때 null이나 undefined와 같고, 그 이외의 값과는 같지 않기 때문에 false를 반환한다.

유의할 점

지금까지 살펴본 undefinednull과 관련해 일어날 수 있는 예외 상황을 아래와 같은 방법을 통해 예방하자.

  • ===를 제외한 비교 연산자의 피연산자에 undefinednull이 오지 않도록 주의하자.
  • 또한 명확한 의도를 갖고 있지 않은 이상 undefinednull이 될 가능성이 있는 변수가 <, >, <=, >=의 피연산자가 되지 않도록 주의하자. 만약 변수가 undefinednull이 될 가능성이 있다고 판단되면, 이를 따로 처리하는 코드를 추가하자.

동등 비교 연산자와 명시적 형변환

흥미로운 상황

let a = 0;
console.log(Boolean(a)); // false

let b = '0';
console.log(Boolean(b)); // true

console.log(a == b); // true

a와 b 두 값을 비교하면 true가 반환되는데, 값을 각각 논리 평가하면 하나는 true, 하나는 false을 반환한다는 점에 의아할 수 있다. 자바스크립트에선 자연스러운 결과이다. 동등 비교 연산자 ==는 피연산자를 숫자형으로 바꾸지만(위에서 문자열 "0"을 숫자 0으로 변환시킨 것처럼), Boolean을 이용한 명시적 변환에서는 다른 규칙이 적용되기 때문이다.

👉 자바스크립트에서의 형변환(명시적 형변환, 암묵적 형변환) 자세히 보러가기

(추가) 여러가지 값을 숫자로 변환해보자

// 여러가지 값을 숫자로 변환해보자.
Number(null); // 0 // null은 숫자로 변환하면 0이다.
Number(undefined); // NaN
Number(''); // 0
Number('      '); // 0
Number(false); // 0
Number([]); // 0
Number({}); // NaN

뚱이가 가르쳐주는 자바스크립트 수수께끼

image

0 == '0'0 == []true인데, '0' == []false일까? 차근차근 살펴보자.

1) 0'0' 비교하기

위에서 봤던 것 처럼 == 연산의 경우 두 대상의 타입이 다르면 형변환한 후에 값을 비교한다. 문자열인 '0'이 숫자형인 0으로 변환되고 값의 비교가 진행된다.

[Step 1] -> 0 == "0"
[Step 2] -> 0 == (Number) "0"

[Result] = true

2) 0[] 비교하기

[]은 배열이고 배열은 객체에 속한다. 자바스크립트에서 원시타입과 객체를 비교할 때는 객체에 ToPrimitive라는 추상 연산을 가한 후 얻은 값을 통해 비교하게 되어 있다고 한다. ToPrimitive 추상 연산에서는 먼저 객체의 DefaultValue가 될 요소를 찾아야 하는데 그 과정은 다음과 같다.

    1. 객체에 valueOf()를 적용한 뒤, valueOf()의 결과가 원시타입인지 확인한다.
    1. 원시타입이 아니라면 toString()을 적용한다.
    1. 만약 둘 다 적용이 불가하다면 타입 에러를 출력한다.

빈 배열에 위 과정을 적용하면 다음과 같다.

let a = [];

a.valueOf();
// []

a.toString();
// ""

빈 배열([])에 valueOf() 메서드를 적용하게 되면 빈 배열을 얻게 되고 이는 원시타입이 아닌 참조 타입이므로 toString() 연산을 적용하게 된다. toString()을 적용하면 빈 배열은 빈 문자열이 된다. 마지막에는 이 빈 문자열을 숫자형으로 변환한 결과인 0과 좌측 피연산자인 0을 비교하게 되므로 이 비교의 결과도 true가 된다.

[Step 1] -> 0 == [] // 직접 비교 불가, []에 toPrimitive 연산 적용
[Step 2] -> 0 == ""
[Step 3] -> 0 == (number) ""

[Result] = true

3) '0'[] 비교하기

1), 2)에서 다룬 내용들을 동원하면 마지막 수수께끼도 풀 수 있다.

원시 타입과 객체 타입을 비교하면 객체 타입에서 ToPrimitive라는 연산이 진행되는 것을 위에서 보았다. 그리고 빈 배열에 ToPrimitive 연산을 적용하면 결과는 빈 문자열이었다.

따라서 '0' == []'0' == ''가 되고, '0'''는 타입은 문자열로 같지만 값은 다르므로 비교 결과는 false가 된다.

[Step 1] -> "0" == [] // 직접 비교 불가, []에 toPrimitive 연산 적용
[Step 2] -> "0" == ""
[Step 3] -> "0" == "" // 비교 수행

[Result] = false

헷갈리는 예제들

위에서 봤던 뚱이와 광선맨 문제

0 == '0';
0 == [];
'0' == [];
정답
0 == '0'; // true
0 == []; // true
'0' == []; // false

문제

0 == '\n0\n';

'' == '0';
0 == '';

false == 'false';
false == '0';

false == undefined;
false == null;
null == undefined;

' \t\r\n' == 0;
정답
0 == '\n0\n'; // true

'' == '0'; // false, 문자열은 사전순으로 비교한다.
0 == ''; // true

false == 'false'; // false
false == '0'; // true

false == undefined; // false
false == null; // false
null == undefined; // true

' \t\r\n' == 0; // true

💡 자바스크립트 이스케이프 시퀀스

\(백슬래시)를 이용하면 자바스크립트에서 특수문자를 나타낼 수 있다.

ex) \t: 탭(Tab), \n: 줄 바꿈(엔터), \r: 캐리지 리턴(CR), \b: 백스페이스

👉 자바스크립트 이스케이프 시퀀스


문제

5 > 4;
'apple' > 'pineapple';
'2' > '12';
undefined == null;
undefined === null;
null == '\n0\n';
null === +'\n0\n';
정답
5 > 4; // true
'apple' > 'pineapple'; // false, 문자열 비교는 사전순 기준. "a"는 "p"보다 작다.
'2' > '12'; // (1) false, 문자열 비교는 사전순 기준, 왼쪽 피연산자의 첫번째 글자 "2"는 오른쪽 피연산자의 첫번째 글자 "1"보다 크다.
undefined == null; // true, ==로 비교할 때 null과 undefined는 같다.(위에서 배웠다.)
undefined === null; // false, 둘의 자료형이 다르므로 false이다.
null == '\n0\n'; // false, ==로 비교할 때 null은 오직 null과 undefined와 같다.
null === +'\n0\n'; // false, 자료형이 다르므로 false가 반환된다. // 단항 더하기 연산자(+)는 valueOf() 연산이다.

💡 문자열을 비교할 때는 사전순, 정확히 말하면 유니코드순으로 한 문자, 한 문자씩 비교를 진행한다. + (1)은 문자열과 문자열의 비교이므로 어떤 것도 숫자형으로 형변환되지 않는다.
👉 자바스크립트에서의 형변환(명시적 형변환, 암묵적 형변환) 자세히 보러가기

🤷‍ 그래서 결국 둘의 핵심적인 차이점이 뭘까? 어떤 상황에서 각각을 써야할까?

(예정)

Ref

profile
정리하고 모으고 커뮤니케이션하는 걸 좋아하는 새싹 웹 개발자🌱

0개의 댓글