[ You Don’t Know JS 정리 ]

2. 값


2.1 배열
      2.1.1 유사 배열
2.2 문자열
2.3 숫자
      2.3.1 숫자 구문
      2.3.2 작은 소수 값
      2.3.3 안전한 정수 범위
      2.3.4 정수인지 확인
      2.3.5 32비트 (부호 있는) 정수
2.4 특수 값
      2.4.1 값 아닌 값
      2.4.2 Undefined
      2.4.3 특수 숫자
      2.4.4 특이한 동등 비교
2.5 값 vs 레퍼런스


2.1 배열

  • 어떤 타입의 값이라도 담을 수 있는 그릇

  • 배열 크기는 미리 정하지 않고도 선언할 수 있으며 원하는 값을 추가하면 된다.

  • 빈/빠진 슬롯이 있는 ‘구멍 난’ 배열을 다룰 때는 조심

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var a = [];
    a[0] = 1;
    a[2]= [3]; // a[1] 건너뜀

    a[1]; // undefined
    a.length; // 3

    /*
    명시적으로 a[1] = undefined 준 것과 똑같지는 않다.
    */
  • 배열의 인덱스는 숫자인데, 배열 자체도 하나의 객체여서 키/프로퍼티 문자열을 추가할 수 있지만 배열 length가 증가하지는 않는다

    1
    2
    3
    4
    5
    6
    var a = [];
    a[0] = 1;
    a["foobar"] = 2;

    a.length; //1
    a.foobar; // 2
  • 키로 숫자형태의 문자열이 들어가면, 마치 문자열 키가 아닌 숫자 키를 사용한 것과 같은 결과를 초래

    1
    2
    3
    var a = [];
    a["13"] = 42;
    a.length; // 14
  • 일반적으로 배열에 문자열 타입의 키/프로퍼티를 두는 것을 추천하지 않는다. 그렇게 해야 한다면 객체를 대용하고 배열 원소의 인덱스를 확실히 숫자만 쓰자


2.1.1 유사 배열

  • 유사 배열 값을 진짜 배열로 바꾸고 싶을 때
    • indexOf(), concat(), forEach() 등을 사용하는 것이 일반적
    • Array.prototype.slice.call() : slice()에 인자가 없으면 기본 인자 값으로 구성된 배열을 복사한다.
    • ES6부터 Array.from() 이 이 일을 대신한다.

2.2 문자열

  • 문자열은 유사배열

    1
    2
    3
    var a = new String('23')
    a; // String {"23"}
    // 0: "2" 1: "3"
  • .length , .indexOf(), .concat() 메서드 가짐

  • 하지만 문자열 !== 배열

    • 문자열 : 불변 값(immutable value)

      • 문자열 메서드는 그 내용을 바로 변경하지 않고 항상 새로운 문자열을 생성한 후 반환한다.
    • 배열 : 가변 값(mutable value)

      • 대부분의 배열 메서드는 그 자리에서 곧바로 원소를 수정한다.
    • a[1] 처럼 문자열의 특정 문자를 접근하는 형태가 모든 자바스크립트 엔진에서 유효하지는 않다. (IE 8 이전까지는 이를 문법 에러로 인식) => a.charAt(1)로 접근해야 맞다.

    • 문자열을 다룰 때 유용한 대부분의 배열 메서드는 사실상 문자열에 쓸 수 없지만 빌려 쓸 수는 있다.

      1
      2
      3
      4
      5
      var a = "foo";
      var b = ["f", "o", "o"];

      a.reverse();// undefined
      b.reverse(); //["o", "o", "f"]
    • 문자열을 배열 => 원하는 작업 수행 => 다시 문자열로 되돌리기

      1
      2
      var c = a.split("").reverse().join("")
      c; // "oof"

2.3 숫자

  • 숫자 타입은 number가 유일
  • 정수, 부동 소수점 숫자 모두 아우른다

2.3.1 숫자 구문

  • 자바스크립트 숫자 리터럴은 10진수 리터럴로 표시한다.

    1
    2
    var a = 42;
    var b = 42.3;
  • 소수점 앞 정수가 0이면 생략 가능

    1
    2
    var a = 0.42;
    var b = .42;
  • 소수점 이하가 0일 때도 생략 가능

    1
    2
    var a = 42.0;
    var b = 42;
    • 하지만 42. 처럼 표기하는 것은 일반적이지도 않고 다른 사람이 코드를 읽을 때 혼동을 일으킬 수 있으니 권장하지 않는다.
  • toExponential(): 아주 크거나 아주 작은 숫자를 지수형으로 표시

  • toFixed(): 지정한 소수점 이하 자릿수까지 숫자를 나타냄

  • 위 메서드들은 숫자 리터럴에서 바로 접근할 수 있으므로 굳이 변수를 만들어 할당하지 않아도 된다. 하지만 .이 소수점일 경우엔 프로퍼티 접근자가 아닌 숫자 리터럴의 일부로 해석되므로 주의하자.

    1
    2
    3
    4
    5
    42.toFixed(3) // SyntaxError
    // 42.이 리터럴의 일부가 되어버려 .toFixed 메서드에 접근할 .이 없다

    (42).toFixed(3); //"42.000"
    0.42.toFixed(3); // "0.420"
  • 0다음 대문자 O는 헷갈리니 언제나 소문자로 0o, 0b 와 같이 표기하자


2.3.2 작은 소수 값

  • 이진 부동 소수점 숫자의 부작용

    1
    0.1 + 0.2 === 0.3 //false
    • 실제로는 0.30000000000000004에 가깝기 때문에 정확하게 0.3이 아니다.
    • 가장 일반적으로 미세한 ‘반올림 오차’(머신 입실론)를 허용 공차로 처리하는 방법
    • Number.EPSILON (ES6부터)

2.3.3 안전한 정수 범위

  • 안전하게 표현할 수 있는 (즉. 표현한 값과 실제 값이 정확하게 일치한다고 장담할 수 있는) 정수는 최대 9천 조가 넘는 수
  • 9천 조가 넘는 수의 사용해야 한다면 Big Number utility 사용을 권장

2.3.4 정수인지 확인

  • ES6부터는 Number.isInteger()로 어떤 값의 정수 여부를 확인한다.
    1
    Number.isInteger(42); //true

2.3.5 32비트 (부호 있는) 정수

스킵

2.4 특수 값

2.4.1 값 아닌 값

  • undefinednull은 타입과 값이 같다
  • null은 식별자가 아닌 특별한 키워드이므로 null이라는 변수에 뭔가를 할당할 수 없다.

2.4.2 Undefined

  • Normal mode에서는 전역 스코프에서 undefined라는 식별자에 값을 할당할 수 있다. (절대 추천하지 않음)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function foo() {
    undefined = 2;
    }
    foo();

    function foo() {
    'use strict'
    undefined = 2; // 타입 에러
    }
    foo()
  • Void 연산자

    • 표현식 void는 어떤 값이든 ‘무효’로 만들어, 항상 결과값을 undefined로 만든다.

    • void 연산자는 어떤 표현식의 결과값이 없다는 것을 확실히 밝혀야할 때 좋다

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function doSomething() {
      if(!App.ready) {
      return void setTimeout(doSomething, 100);
      // setTimeout은 숫자 값을 반환 하지만 이 숫자 값을 무효로 만들어
      }
      var result;
      return result;
      }
      if(doSomething()) {
      // doSomething()의 결과값이 if 문에서 긍정 오류가 일어나지 않게 했다.
      }
    • 하지만 밑에 코드처럼 두 줄로 분리해서 쓰는 것을 선호하는 개발자들이 많고 void연산자는 잘 쓰지 않는다.

      1
      2
      3
      4
      if(!App.ready) {
      setTimeout(doSomething, 100);
      return;
      }
    • 어떤 표현식으로부터 값이 존재하는 곳에서 그 값이 undefined가 되어야 좋을 경우에만 사용하자

      2.4.3 특수 숫자

  • NaN(Not A Number)

    • 두 피연산자가 전부 숫자가 아닐 경우 유효한 숫자가 나올 수 없으므로 그 결과는 NaN이다.

    • 경계 값의 일종으로 숫자 집합 내에서 특별한 종류의 에러 상황을 나타낸다.

    • NaN은 어떤 NaN과도 동등하지 않다.(즉, 자기 자신과도 같지 않다.)

    • 반사성이 없는(x === x로 식별되지 않는) 유일무이한 값

    • 내장 전역 유틸리티isNaN()함수로 NaN여부를 알 수 있다.

      1
      2
      3
      4
      5
      6
      7
      8
      var a = 2 / "foo";
      var b = "foo";

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

      window.isNaN(a); //true
      window.isNan(b); //true
      • “foo”는 당연히 숫자가 아니지만 그렇다고 NaN은 아니다. 이를 해결하기 위해 Number.isNaN()이 ES6부터 등장
  • 무한대

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

      1
      2
      var a = 1 / 0 ; //Infinity
      var b = -1 / 0; //-Infinity
    • 자바스크립트는 유한 숫자 표현식을 사용하므로 +무한대/ -무한대가 될 수 있다.

    • 무한대 / 무한대 : NaN

    • 0 / 유한한 양수 : 0


  • 0(영)

    • 음의 영은 표기 뿐만 아니라 연산 결과 또한 -0으로 떨어진다.

      1
      var a = 0 / -3; //-0;
    • 하지만 명세에 의하면 -0을 문자열화하면 항상 “0”이다.

      1
      2
      a.toString(); //"0"
      JSON.stringify(a); //"0"
    • 반대로 하면 있는 그대로를 보여준다.

      1
      2
      Number("-0"); //-0
      JSON.parse("-0");//-0
    • 왜 -0을 만들었을까?

      • 값의 크기로 어떤 정보(ex: 애니메이션 프레임당 넘김 속도)와 그 값의 부호로 또 다른 정보(ex: 넘김 방향)를 동시에 나타내야 하는 어플리케이션이 있기 때문
      • 잠재적인 정보 소실을 방지하기 위해 0의 부호를 보존한 셈

2.4.4 특이한 동등 비교

  • NaN-0의 동등 비교가 까다로웠는데 ES6부터는 Object.is()라는 유틸리티를 지원한다.

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

    Object.is(a, NaN); //true
    Object.is(b, -0); //true
  • =====가 안전하다면 굳이 Object.is()는 사용하지 않는 편이 좋다. 기본 연산자가 좀 더 효율이 좋고 일반적이기 때문.


##2.5 값 VS 레퍼런스

  • 값의 타입만으로 값-복사 혹은 레퍼런스-복사가 결정된다.

    • 원시 값 : 값-복사

    • 레퍼런스 값 : 레퍼런스-복사

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      var a = 2;
      var b = a; //b는 언제나 a에서 값을 복사한다.
      b++;
      a;// 2
      b;// 3

      var c = [1,2,3];
      var d = c; // d는 공유된 [1,2,3]값의 레퍼런스다.
      d.push(4);
      c;// [1,2,3,4]
      d;// [1,2,3,4]
    • 2는 원시 값이므로 a에는 2의 초기 사본이, b에는 또 다른 사본이 들어간다. 따라서 b를 바꿔도 a까지 동시에 바뀔 수 없다.

    • cd는 모두 함성 값이자 동일한 공유 값 [1,2,3]에 대한 개별 레퍼런스다. (단지 [1,2,3]을 동등하게 참조만!한다는 사실) 따라서 레퍼런스로 실제 공유한 배열 값이 변경되면, 이 공유 값 한 군데에만 영향을 미치므로 두 레퍼런스는 갱신된 값을 동시에 바라보게 된다.

    • 레퍼런스는 변수가 아닌 값 자체를 가리키므로 a 레퍼런스로 b 레퍼런스가 가리키는 대상을 변경할 수 없다.

      1
      2
      3
      4
      5
      6
      7
      8
      var a = [1,2,3]
      var b = a;
      a;//[1,2,3]
      b;//[1,2,3]

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

    • call by value / call by reference

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function foo(x) {
      x.push(4);
      x; //[1,2,3]

      x = [4,5,6];
      x.push(7);
      x; //[4,5,6,7]
      }

      var a = [1,2,3];

      foo(a);

      a; // [1,2,3,4]
      • a를 인자로 넘기면 a의 레퍼런스 사본이 x에 할당 => xa는 동일한 [1,2,3]을 가리키는 별도의 레퍼런스 => 함수 내부에서 레퍼런스를 이용하여 값 자체를 변경 => x = [4,5,6]으로 새 값을 할당해도 초기 레퍼런스 a가 참조하고 있던 값에는 아무런 영향이 없다.
      • 속성 변경 => 변경
      • 재할당 => 서로 다른 객체 바라봄
    • 배열 같은 합성 값을 값-복사로 전달하려면 값의 사본을 만들어 전달한 레퍼런스가 원본을 가리키지 않게 하면 된다.

      1
      foo(a.slice());
      • 인자 없이 slice()를 호출하면 전혀 새로운 배열의(shallow copy)사본을 만든다. 이렇게 복사한 사본만을 가리키는 레퍼런스를 전달하니 foo()a의 내용을 건드릴 수 없다.
    • 원시값을 레퍼런스-복사로 전달하려면 원시 값을 다른 합성값으로 감싸야한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      function foo(wrapper) {
      wrapper.a = 42;
      }

      var obj = {
      a:2
      }
      foo(obj);
      obj.a;// 42
      • obj는 원시 프로퍼티 a를 감싼 wrapper => foo()obj 레퍼런스 사본이 전달 => 래퍼 인자의 값을 바꿈 => 래퍼 레퍼런스로 공유된 객체에 접근하여 프로퍼티 수정