본문 바로가기
이론/Frontend

자바스크립트에서 값 비교하기

by 유세지 2023. 4. 23.

 

 

 

 

들어가며

자바스크립트에서 값을 비교하는 방법으로는 크게 세 가지가 있습니다.

 

  • == 를 사용하는 느슨한 비교 (loose equality)
  • === 를 사용하는 엄격한 비교 (strict equality)
  • Object.is() 를 사용하는 비교

 

느슨한 비교는 타입이 다르더라도, 변환했을때 값이 같으면 true를 반환하는 비교입니다. 때문에 비교 대상끼리 암시적 형변환이 일어나는 비교라고 볼 수 있습니다.

 

엄격한 비교는 값이 동일하더라도 타입이 다르면 false를 반환하는 비교입니다. 비교 로직 중 어떠한 형변환도 일어나지 않습니다.

 

Object.is()를 사용하는 비교는 일단 두 값이 같은지만을 검사하는 비교입니다. 엄격한 비교와 마찬가지로 형변환이 일어나지 않습니다.

 

/* 1. 느슨한 비교 */

'36' == 36 // true

/* 2. 엄격한 비교 */

'36' === 36 // false

/* 3. Object.is() 비교 */

Object.is('36', 36) // false
Object.is(36, 36) // true

 

느슨한 비교에서 일어나는 묵시적 형변환에 대해서는 이전에 포스팅으로 정리해두었습니다.

JavaScript의 강제변환 - 3 (Coercive Type Conversion of JavaScript)

 

 

얼핏 보기에 엄격한 비교와 Object.is() 는 차이가 없어 보입니다. 하지만 Object.is()는 ES6에서 새로 제정된 내장 메서드입니다. 정말 두 비교가 동일하다면, 새롭게 메서드가 만들어질 이유는 없을 것입니다. 그렇다면, Object.is() 는 어떤 이유에서 만들어진걸까요?

 

 

Same-Value와 Same-Value-Zero

그 차이를 확인하려면 Object.is() 가 먼저 어떤 알고리즘을 통해 비교를 수행하는지를 알아야합니다. Object.is() 의 경우 값 비교에 Same-Value 알고리즘을 따릅니다. 이 알고리즘은 다음과 같은 과정을 거칩니다.

 

 

Same-Value-Equality

  1. 두 타입이 같은지 확인합니다. 만약 다르다면 false를 반환합니다.
  2. 만약 비교 대상의 타입이 숫자(Number)라면, Number::SameValue 알고리즘을 따릅니다.
  3. 비교 대상이 그 외의 타입이라면 SameValueNonNumber 알고리즘을 따릅니다.

 

Same-Value-Equality가 일반적인 엄격한 비교와 다른 점은 숫자와 숫자가 아닌, 두 개의 타입을 나누어 처리한다는 점입니다. Number::SameValue 알고리즘도 일반적인 숫자 비교는 다른 알고리즘들과 동일하지만, 0과 NaN을 비교하는 부분에서 차이가 있습니다.

 

 

Number::SameValue

  1. 두 비교 대상이 NaN이라면, true를 반환합니다.
  2. 두 비교 대상이 +0, -0 인 경우 false를 반환합니다.
  3. 두 비교 대상이 같다면 true를 반환합니다.
  4. 이 외에는 false를 반환합니다.

 

먼저 NaN을 보겠습니다. 우리가 알고 있다시피, NaN의 타입은 Number입니다.

 

typeof(NaN)은 number입니다.

 

숫자가 아닌 타입을 숫자로 변환하려고 하거나, 0으로 나누려고 하는 등 잘못된 숫자 연산을 시도하면 Not A Number(NaN) 을 받게 됩니다. 의미만 보면 숫자가 아니라는 뜻이지만, 아이러니하게도 타입은 Number입니다.

 

IEEE-754에서 제공하는 가이드에 따르면 NaN의 경우 NE(Not Equal) 연산을 제외한 모든 연산자에서 false를 반환하도록 하고 있습니다. 이 때문에 엄격한 비교의 경우 NaN은 자기 자신을 포함한 그 어떤 값과 비교해도 false를 반환합니다. NaN은 부정형 실수이자 일종의 에러 상태이니 애초에 비교의 대상이 될 수 없다는걸 생각해보면, 연산에서 false를 반환하는건 나름 일리가 있어 보입니다. 하지만 이러한 배경을 모르는 상태에서 봤을때는 직관적으로 받아들이기 어렵습니다. 실제로 이러한 비교를 수행해야 하는 경우가 종종 발생하기도 하구요.

 

때문에 Object.is() 의 경우 이러한 예외를 두어 NaN 끼리의 비교에서는 true를 반환하도록 하는 Number::SameValue 알고리즘을 따르게 되었습니다.

 

 

/* 1. 느슨한 비교 */

NaN == NaN // false

/* 2. 엄격한 비교 */

NaN === NaN // false

/* 3. Object.is() 비교 */

Object.is(NaN, NaN) // true

 

 

두번째는 +0과 -0의 비교입니다. 수학적인 관점에서 보면, 0은 정수 중에서도 양수도 음수도 아닌 별도의 개념인데 여기에 부호가 붙는것이 다소 어색하게 보입니다. 그러나 IEEE-754 를 참고하면, 0의 부호를 구분하고 있다는 것을 알 수 있습니다. 이는 IEEE-754에서 실수(float)를 표현하는 방식을 보면 이해가 쉽습니다.

 

 

1비트의 부호부, 8비트의 지수부, 23비트의 가수부로 이루어진 실수 체계

 

IEEE-753 에서는 실수를 총 32비트의 이진수로 표현합니다. 그 중 부호부의 비트에 따라 양수인지 음수인지가 결정되는데, 그 때문에 지수부와 가수부의 비트가 모두 0이더라도 부호부의 비트로 -0과 +0이 결정됩니다. 실제로 값은 0이지만, 방향이 생겨버린 셈입니다.

 

1/0 === 1/-0 // infinity === -infinity : false

 

Same-Value 를 따르는 Object.is() 로 확인해보면, 이러한 값들이 확실히 구분됩니다.

 

/* 1. 느슨한 비교 */

0 == -0 // true

/* 2. 엄격한 비교 */

0 === -0 // true

/* 3. Object.is() 비교 */

Object.is(0, -0) // false

 

Same-Value-zero 는 이러한 0 처리를 위한 알고리즘입니다. 다른 부분들은 Same-Value와 동일하면서, 0과 -0을 같은 값으로 취급한다는 점만 다릅니다.

 

Same-Value-zero 는 Same-Value 의 Object.is() 처럼 따로 JavaScript에서 사용할 수 있는 메서드는 노출(not expose)되어 있지는 않지만, 대신 Array.prototype.includes(), TypedArray.prototype.includes() 등의 메서드나 Map과 Set의 키 값을 비교하는 등의 내부적인 동작에 적용되고 있습니다.

 

 

 

 

 

마치며

 

JavaScript의 Equlity 비교 결과를 나타낸 표

 

 

이제 Object.is() 가 느슨한 비교나 엄격한 비교와 어떤 점이 다른지 알 수 있게 되었습니다. 그 중 가장 중요한 차이점인 NaN에 대한 처리Signed zero라고 부르는 0의 부호에 대한 처리가 기존의 비교와 다르다는 것을 알아두시면 좋을 것 같습니다. 더불어서 기반이 되는 알고리즘인 Same-Value와 Same-Value-Zero도 함께 기억해두시면 더욱 좋겠네요.

 

느슨한 비교와 엄격한 비교, 그리고 Object.is() 를 사용해 비교를 진행하실때 제 글이 조금이나마 도움이 되셨다면 좋겠습니다. 읽어주셔서 감사합니다.

 

 

 

참고문서

Equality In JavaScript - dorey

SameValue - ECMAScript Language Specification

Equality comparisons and sameness - MDN Docs

 

 

 

 

반응형

댓글