본문 바로가기
JAVASCRIPT

[JS][값] 특수 값

by KBS 2022. 1. 26.
728x90

특수 값

타입별로 자바스크립트 개발자들이 조심해서 사용해야할 특수한 값들이 있다.

값 아닌 값

undefined타입의 값은 undefined밖에 없다. null타입도 값은 null뿐이다. 그래서 이 둘은 타입과 값이 항상 같다.

undefinednull은 종종 '빈'값과 '값 아닌'값을 나타낸다. 이와 다른 의미로 사용하는 개발자도 있다. 예를들면,

  • null은 빈 값이다.
  • undefined는 실종된 값이다.

또는

  • null은 예전에 값이 있었지만 지금은 없는 상태다.
  • undefined는 값을 아직 가지지 않은 것이다.

undefinednull의 의미를 어떻게 '정의'하여 쓰든지, null은 식별자가 아닌 특별한 키워드 이므로 null이라는 변수에 뭔가 할당할 수 는 없다. 오! 이런.. 그런데 불행히도 undefined는 식별자로 쓸 수 있다.

Undefined

느슨한 모드에서는 전역 스코프에서 undefined란 식별자에 값을 할당할 수 있다.(절대 추천하지 않는다.)

 

function foo() {
  undefined = 2; // 진짜 진짜 비추천한다.
}

foo();

function foo() {
  "use strict";
  undefined = 2; // 타입에러 발생!!
}

foo();

 

그런데 모드에 상관없이 undefined란 이름을 가진 지역 변수는 생성할 수 있다. 가능하기는 하지만 끔직한 짓이다.

 

function foo() {
  "use strict";
  var undefined = 2;
  console.log(undefined); // 2
}

foo();

 

좋은 친구라면 undefined를 여러분이 재정의 하도록 내버려 두지 않을 것이다.

 

void 연산자

undefined는 내장 식별자로 값은 undefined지만, 이 값은 void연산자로도 얻을 수 있다. 표현식 void __는 어떤 값이든 '무효로 만들어'. 항상 결과 값을 undefined로 만든다. 기존 값은 건드리지 않고 연산 후 값은 복구할 수 없다.

 

var a = 42;

console.log(void a, a); // undefined 42

 

관례에 따라 void만으로 undefined 값을 나타내려면 void 0이라고 쓴다. void 0, void 1, undefined 모두 같다.

void연산자는 어떤 표현식의 결괏값이 없다는 걸 확실히 밝혀야 할 때 긴요하다. 예를들어

 

function doSomething() {
  // 참고 :  APP.ready는 이 애플리케이션에서 제공한 값이다.
  if (!APP.ready) {
    // 나중에 다시 해보자
    return void setTimeout(doSomething, 100);
  }

  var result;
  // 별도 처리 수행
  return result;
}

// 제대로 처리했니?
if (doSomething()) {
  // 다음작업 바로실행
}

 

setTimeout() 함수는 숫자 값(타이머를 취소할 때 사용할 타이머의 고유 식별자)을 반환하지만, 예제 에서는 이 숫자 값을 무효로 만들어 doSomething()함수의 결괏값이 if문에서 긍정 오류가 일어나지 않게 했다.

이때 다음 코드처럼 두 줄로 분리해 쓰는 걸 선호하는 개발자들이 많고 void 연산자는 잘 쓰지 않는다.

 

if (!APP.ready) {
  // 나중에 다시 해보자!
  setTimeout(doSomething, 100);
  return;
}

 

정리하면 void연산자는 값이 존재하는 곳에서 그 값이 undefined가 되어야 좋을 경우에만 사용하자. 아마도 그렇게 해야 할 경우도 거의 없고 극히 제한적으로 쓰이겠지만 제법 쓸모는 있다.

 

특수 숫자.

숫자 타입에는 몇 가지 특수한 값이 있다. 하나씩 알아보자.

 

The not number, number

 

수학 연산 시 두 피연산자가 전부 숫자가 아닐 경우 유효한 숫자가 나올 수 없으므로 그 결과는 NaN이다. NaN은 글자 그대로 '숫자 아님(Not A Number)'이다. 그런데 이 명칭과 설명이 아주 형편없고 오해의 소지가 다분하다. NaN은 숫자 아님 보다는 유효하지 않은 숫자, 실패한 숫자 또는 몹쓸 숫자라고 하는게 차라리 더 정확하다.

 

var a = 2 / "foo"; // NaN

typeof a === "number"; // true

 

즉, '숫자 아님의 typeof는 숫자다!'란 뜻이다. 숫자가 아니라며 숫자라는 해괴망측한 이름과 의미를 가지고있다.

NaN은 경계 값의 일종으로 숫자 집합 내에서 특별한 종류의 에러상황("나는 당신이 내준 수학 연산을 해봤지만 실패했어 그러니 여기 실패한 숫자를 도로 가져가시지")을 나타낸다.

어떤 변숫값이 특수한 실패 숫자, 즉 NaN인지 여부를 확인할 때 null, undefined처럼 NaN도 직접 비교하고 싶은 충동이 생기겠지만.. 틀렸다.

 

var a = 2 / "foo";

a == NaN; // false
a === NaN; // false

 

NaN은 귀하신 몸이라 다른 어떤 NaN과도 동등하지 않다.(즉, 자기 자신과도 같지 않다.) 사실상 반사성이 없는 유일무이한 값이다. 따라서 NaN !== NaN이다.

비교 불가능이라면 그럼 NaN여부는 어떻게 확인할까?

 

var a = 2 / "foo";

isNaN(a); // true

 

내장 전역 유틸리티 isNaN() 함수가 NaN여부를 확인한다. 문제 해결!!! 인줄 알았겠지만 아니다. isNaN()는 치명적인 결함이 있다. 이 함수는 NaN의 의미를 너무 글자 그대로만 해석해서 실제로 '인자 값이 숫자인지 여부를 평가'하는 기능이 전부다. 하지만 이래서는 결과가 적확할 수 없다.

 

var a = 2 / "foo";
var b = "foo";

a; // NaN
b; // "foo"

window.isNaN(a); // true
window.isNaN(b); // true ...?

 

"foo"는 당연히 숫자가 아니지만, 그렇다고 NaN는 아니다! 이 버그는 자바스크립트 탄생 이후 오늘까지 계속됬다. 19년이 넘도록..

드디어 ES6부터는 해결사 Number.isNaN()이 등장했다. ES6 이전 브라우저에서는 다음 폴리필을 쓰면 안전하게 NaN여부를 체크하 수 있다.

 

if (!Number.isNaN) {
  Number.isNaN ===
    function (n) {
      return typeof n === "number" && window.isNaN(n);
    };
}

var a = 2 / "foo";
var b = "foo";

Number.isNaN(a); // true
Number.isNaN(b); // false !!

 

NaN이 자기 자신과도 동등하지 않는 독특함을 응용하여 폴리필을 더 간단히 구현할 수도 있다. NaN은 세상의 모든 언어를 통틀어 "자기 자신과도 동등하지 않은" 유일한 값이다.

 

if (!Number.isNaN) {
  Number.isNaN = function (n) {
    return n !== n;
  };
}

 

이상하게 보이지만 잘 작동한다!! 실제로 많은 자바스크립트 코드에 NaN은 고의 또는 실수로 박혀있다. 의미를 오해하지 않고 바르게 쓰려면 Number.isNaN같은 내장 유틸리티를 사용하다. 만약 지금 여러분이 isNaN()를 사용하여 코딩 중이라면, 유감스럽게도 아직 터지지 않은 지뢰를 묻어 놓은 셈이다.

 

무한대

 

C와 같은 전통적인 컴파일 언어를 쓰는 개발자들은 '0으로 나누기'비슷한 컴파일/런타임 에러를 숱하게 보았을 것이다.

 

var a = 1 / 0;

 

그러나 자바스크립트에서는 0으로 나누기 연산이 잘 정의되어 있어서 에러 없이 Infinity(Number.POSITIVE_INFINITY)라는 결과값이 나온다.

 

var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity

 

분자가 음수면 0으로 나누기 결과 값은 -Infinity다. 자바스크립트는 유한 숫자 표현식을 사용하므로 수학 교과서와는 다르게 덧셈, 뺄셈 같은 연산 결과가 +무한대/-무한대가 될 수 있다.

"무한을 무한으로 나누면?" 이거 단순히 생각하면 '1'이나 '무한대'가 될 것 같지만 모두 틀렸다. 수학책, 자바스크립트 모두 무한대/무한대는 '정의되지 않는 연산'이며, 결괏값은 NaN이다!

유한한 양수를 무한대로 나누면? 당연히 0, 이건 쉽다 그렇다면 음수를 무한대로 나누면?

 

영(0)

자바스크립트엔 보통 영(+0)과 음의 영(-0)이 있다는 사실 자체가 혼란스러울 수도 있다. -0의 존재 이유를 설명하기 전에 자바스크립트가 영을 다루는 방식을 먼저 짚어보자.

음의 영은 표기만 -0으로 하는 것이 아니다. 특정 수식의 연산 결과 또한 -0으로 떨어진다.

 

var a = 0 / -3; // -0
var b = 0 * -3; // -0

 

덧셈과 뺄셈에는 -0이 나올 일이 없다. 개발자 콘솔 창에서 확인해보면 -0으로 나오겠지만, 비교적 최근 가지 자주 나오는 연산은 아니여서 아직도 0으로 표시되는 브라우저도 있다. 그런데 무슨 수학 경시대회도 아니고 -0은 왜만든 것일까?

값의 크기로 어떤 정보 어떤정보와 그 값의 부호로 또 다른 정보를 동시에 나타내야 하는 애플리케이션이 있기 때문이다. +0, -0 개념이 없다면 어떤 변숫값이 0에 도달하여 부호가 바뀌는 순간, 그 직전까지 이 변수의 이동 방향은 무엇인지 알 수가 없으므로 부호가 다른 두 0은 유용하다. 즉, 잠재적인 정보 소실을 방지하기 위해 0의 부호를 보존한 셈이다.

특이한 동등 비교

앞서 설명했듯이 NaN과 -0의 동등 비교는 독특하다. NaN은 자기자신과도 동등하지 않아 ES6의 Number.isNaN()또는 폴리필을 사용해야 하며, 마찬가지로 -0도 거짓말 쟁이라서 보통의 0과 동등한 척 하므로(심지어 엄격 동등 연산자 ===에서도), isNegZero()같은 함수를 꼼수로 써야한다.

 

ES6부터는 잡다한 예외를 걱정하지 않아도 두 값이 절대적으로 동등한지를 확인하는 새로운 유틸리티를 지원한다. 바로 Object.is()다.

 

var a = 2 / "foo";
var b = -3 * 0;

Object.is(a, NaN); // true
Object.is(b, -0); // true
Object.is(b, 0); // false

 

 

ES6 이전 환경에서도 Object.is() 폴리필을 만들어 간단히 쓸 수 있다.

 

if (!Object.is) {
  Object.is = function (v1, v2) {
    // '-0' 테스트
    if (v1 === 0 && v2 === 0) {
      return 1 / v1 === 1 / v2;
    }
    // 'NaN' 테스트
    if (v1 !== v1) {
      return v2 !== v2;
    }

    // 기타

    return v1 === v2;
  };
}

 

=====가 안전하다면 굳이 Object.is()는 사용하지 않는 편이 좋다. 아무래도 기본 연산자가 돔 더 효율이 좋고 알반적이기 때문이다. Object.is()는 주로 특이한 동등 비교에 쓴다.

728x90

'JAVASCRIPT' 카테고리의 다른 글

[JS] this 란?  (0) 2022.01.26
[JS][값][마무리] 값 vs 레퍼런스  (0) 2022.01.26
[JS][값] 숫자  (0) 2022.01.25
[JS][값] 문자열  (0) 2022.01.24
[JS][값] Array  (0) 2022.01.24

댓글