Programming/Javascript

[Javascript] You Don't Know JS (Values-2)

알로그 2020. 3. 8. 12:08
반응형

YOU DON'T KNOW JS : Chapter2. Values

Special Values

JS 개발자가 조심해서 사용해야 하는 여러 특수 값이 있다.

 

The Non-value Values

undefined 타입의 값은 undefined 하나밖에 없다.
null 타입도 null 뿐이다.

몇몇 개발자들은 아래와 같이 구분한다.

  • null is an empty value (null은 빈 값)
  • undefined is a missing value (undefined는 값이 입력되지 않은 값)

또는

  • undefined hasn't had a value yer (undefined는 값을 가진적이 없는 값)
  • null had a value and doesn't anymore (null은 값을 가지고 있었지만 이젠 없는 값)​

 

void Operator

void 연산자는 어떤 값이든 무효로 만들어서 undefined를​ 반환한다.\

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

 

일반적으로 void를 사용하여 정의되지 않은 값을 독립형으로 나타내려면 일반적으로 void 0을 한다.
void 0, void 1 및 undefined 사이에는 실질적인 차이가 없다.

void 연산자는 결과 값이 없는지 확인해야하는 경우 유용 할 수 있다.

function doSomething() {
    // note: `APP.ready` is provided by our application
    if (!APP.ready) {
        // try again later 
        return void setTimeout( doSomething, 100 );
    }

    var result;
    // do some other stuff
    return result;
}

// were we able to do it right away?
if (doSomething()) {
// handle next tasks right away
}

일반적으로 값이 존재할 때 그 값이 undefiend가 될 때 void 연산자를 사용하자.
극히 드문 경우지만 쓸모 있을수 있다.

 

Special Numbers

The Not Number, Number

NaN은 유효하지 않은 숫자로 이해해야한다.
NaN을 typeof 연산자로 확인했을 때 number로 반환하는 것을 볼 수 있다.

var a = 2 / "foo";        // NaN
typeof a === "number";    // true

그렇지만 NaN으로 비교할 수 없다.

var a = 2 / "foo";

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

 

NaN != NaN 이다. 따라서 isNaN() 함수를 이용해서 확인할 수 있지만..​

var a = 2 / "foo";
isNaN( a ); // true

 

isNaN() 함수에 치명적인 결함이 있다. 숫자가 아님임을 확인하기 때문에 'foo'와 같은 문자열도 true로 반환할 수 있다.

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

window.isNaN( a ); // true
window.isNaN( b ); // true -- ouch!


ES6부터는 대체 함수인 Number.isNaN()이 제공되므로 이 함수를 사용해야한다.

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 -- phew!


Infinities

C와 같은 전통적인 컴파일 언어의 개발자는 아마도 '0으로 나누기'와 같은 컴파일러 오류 또는 런타임 예외를 보는 데 익숙 할 것이다.

그러나 JS에서 Infinity (일명 Number.POSITIVE_INFINITY) 값을 반환한다.

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

분자가 음수면 결과값은 -Infinity로 반환한다.

Zeros

마찬가지로 0에도 양수와 음수로 구분될 수 있다..

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

 

-0과 0을 구분하려면 아래와 같은 방법을 사용할 수 있다.

function isNegZero(n) {
    n = Number( n );
    return (n === 0) && (1 / n === -Infinity);
}

isNegZero( -0 );        // true
isNegZero( 0 / -3 );    // true
isNegZero( 0 ); 

사용할 일이 없어보이기 하지만.. 값과 부호로 또 다른 정보(넘김 방향)를 동시에 나타내야 하는 경우 쓰인다고 한다. (애니메이션 프레임 넘김)

Special Equality

위에서 NaN과 -0의 비교 등을 위해 몇몇 함수 사용법을 알아봤는데 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


== 또는 ===가 안전한 경우 Object.is()를 사용하지 않는 것이 더 효율적이다.

Value vs. Reference

​다른 언어에서는 사용하는 구문에 따라 값을 값-복사 또는 참조-복사로 할당하고 전달할 수 있다.
JavaScript에는 포인터가 없으며, 참조하는 방법이 다르다. 다른 변수로의 참조를 가질 수 없다.

JavaScript의 참조는 (공유된) 값을 가 키므로 10 개의 다른 참조가 있으면 항상 공유된 단일 값에 대한 개별적으로 참조한다.

var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3


var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

 

null, undefined, string, number, boolean, symbol은 언제나 값-복사방식으로 할당/전달 된다.
객체나 함수 등 합성 값은 참조 사본을 생성한다.
따라서 ​c와 d 값은 [1,2,3]을 참조하여 배열이 같이 변경됨을 볼 수 있다.

아래와 같이 재할당하면 [1,2,3]은 영향을 받지 않는다.

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]

// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

 

함수에서 이러한 혼동이 가장 많이 발생한다.

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]
​
    // later
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}
​
var a = [1,2,3];​
foo( a );

a; // [1,2,3,4]  not  [4,5,6,7]

x = [4,5,6]을 할당하기 전까지는 같이 [1,2,3]을 참조하기 때문에 [1,2,3,4]가 결과임을 볼 수 있다.

아래 예제에서 x.length=0, x.push(4,5,6,7)은 새배열을 생성하는 코드가 아니라 기존에 공유한 배열을 변경하는 코드이므로 새로운 값인 [4,5,6,7]을 가리키게 된다.

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]

    // later
    x.length = 0; // empty existing array in-place
    x.push( 4, 5, 6, 7 );
    x; // [4,5,6,7]
}

var a = [1,2,3];​
foo( a );
a; // [4,5,6,7]  not  [1,2,3,4]


배열같은 합성값을 값 복사에 의해 효과적으로 전달하려면 값의 사본을 만들어 전달하여 레퍼런스가 원본을 가르키지 않게 하면 된다.

foo( a.slice() );

기본적으로 매개 변수가없는 slice (..)는 배열의 완전히 새로운 (얕은) 사본을 만든다.
따라서 복사 된 배열에만 참조를 전달하므로 foo ()는 a의 내용에 영향을 줄 수 없다.

반대로 스칼라 원시값을 레퍼런스처럼 바뀐 값이 바로 반영되도록 하려면 원시 값을 다른 합성 값(객체, 배열)로 감싸야 한다.

​여기서 obj는 스칼라 프리미티브 속성 a의 래퍼 역할을 한다.

function foo(wrapper) {
    wrapper.a = 42;
}

​var obj = {
    a: 2
};

foo( obj );
obj.a; // 42

 

Review

  • 문자열은 배열과 착각하기 쉽지만 불변의 값이며, 배열로 취급하려면주의를 기울여야한다.
  • JavaScript의 숫자에는 정수와 부동 소수점 값이 모두 포함한다.
  • undefined는 할당된 값이 없다면 모든 변수의 디폴트 값이다.
  • 숫자에는 NaN, Infinity, 0과 같은 특수 값이 있다.
  • 단순 스칼라 값은 값-복사로 전달되고 합성 값(객체, 배열등)은 레퍼런스-복사로 전달된다.
반응형