[코어 자바스크립트] 01. 데이터 타입

·

14 min read

해당 포스팅은 책 "코어 자바스크립트" 를 기반으로 작성된 글 입니다.

데이터 타입의 종류

  • 기본형(원시형, primitive type)
    • number(숫자), string(문자열), boolean(불리언), null, undefined, symbol(심볼, ES6)
  • 참조형(reference type)
    • object(객체), array(배열), Map (ES6), WeakMap (ES6), Set (ES6), WeakSet (ES6)

메모리

  • 자바스크립트는 숫자의 경우 정수형(integer)인지 부동소수형(float)인지를 구분하지 않고 64bit, 즉 8Byte를 확보한다.
  • 각 비트는 고유한 식별자를 지닌다. 바이트 역시 시작하는 비트의 식별자로 위치를 파악할 수 있다.
  • 모든 데이터는 바이트 단위의 식별자, 더 정확하게는 메모리 주소값(memory address) 을 통해 서로 구분하고 연결할 수 있다.

식별자와 변수

  • 변수(variable) : 변할 수 있는 무언가(무언가 = 데이터)
  • 식별자(identifier) : 어떤 데이터를 식볗하는 데 사용하는 이름, 즉 변수명

변수 선언과 데이터 할당

변수 선언

// 변수 선언
var a; 

// 변할 수 있는 데이터를 만들겠다.
// 이 데이터의 식별자는 'a'로 한다.

image.png

  • 변수(variable) : 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇

데이터 할당

var a;     // 변수 a 선언
a = 'abc'  // 변수 a 데이터 할당

var a = 'abc'  // 변수 선언과 할당을 한번에(=초기화)

// 선언과 할당 과정을 나눠서하든, 동시에(=초기화)하든 자바스크립트 엔진은 같은 동작을 수행한다.

image.png

이해하기 쉽게, 데이터 성질에 따라 “변수 영역” 과 “데이터 영역” 으로 구분

  • 데이터를 저장하기 위한 별도의 메모리 공간을 다시 확보해서 문자열 데이터를 저장하고, 그 주수를 변수 영역에 저장하는 식으로 이뤄진다.

왜 변수 영역에 값을 직접 대입하지 않고 굳이 번거롭게 한 단계를 더 거칠까 ?

  • 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더욱 효율적으로 관리하기 위한 고민의 결과
    • 자바스크립트는 숫자형 데이터에 대해 64bit(=8Byte)의 공간을 확보한다.
    • 반면 문자열 데이터는 특별히 정해진 규격이 없다.
      • 한 글자마다 영어는 1Byte, 한글은 2Byte 등으로 각각 필요한 메모리 용량이 가변적이며 글자 수 역시 가변적이기 때문이다.
  • 만약 미리 확보한 공간 내에서만 데이터 변환을 할 수 있다면 변환한 데이터를 다시 저장하기 위해서는 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 선행되어야 한다.

    • 해당 공간이 메모리 상의 가장 마지막에 있었다면 뒤쪽으로 늘리기만 하면 된다.
    • 메모리 상의 중간에 있는 데이터를 늘려야 하는 상황이라면 ?

      • 해당 공간보다 뒤에 저장된 데이터의 메모리 공간들을 전부 뒤로 옮기고, 이동시킨 주소를 각 식별자에 다시 연결하는 작업을 해야한다.
      • 컴퓨터가 처리해야할 연산이 많아질 수밖에 없다.

      결국 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장하는 것이 최적이다.

// 문자열 변환에 대한 메모리 영역의 변화

var a = 'abc' 
a = 'abcdef'  // 변수 a 에 값을 'abc' -> 'abcdef'로 변경

image.png

  • 기존 문자열 데이터가 저장된 공간에 변경할 문자열 데이터를 할당하는 대신, 새로운 문자열을 새로 만들어 별도의 공간에 저장하고, 그 주소를 변수 공간에 연결한다.
    • 기존 문자열에 어떤 변환을 가하든 상관 없이 무조건 새로 만들어 별도의 공간에 저장한다.

만약, N개의 변수를 생성해서 모든 변수의 숫자 5를 할당하는 상황을 생각해보자.

  • 각 변수를 별개로 인식하려면 N개의 변수 공간을 확보하는 것은 불가피하다.
    • 그런데 각 변수 공간마다 매번 숫자 5를 할당하려고 하면 숫자형은 8Byte가 필요하기 때문에 총 N * 8 Byte를 써야한다.
    • 그 대신 숫자 5를 별도의 공간에 한 번만 저장하고 해당 주소만 입력한다면, 중복된 데이터에 대한 처리 효율이 높아진다.
      • 예를 들어, 주소 공간의 크기가 2Byte 라고 한다면 N * 2 + 8 Byte만 이용하면 된다.

기본형 데이터와 참조형 데이터

  • 변수(variable)와 상수(constant)를 구분하는 성질은 변경 가능성 이다.
    • 바꿀 수 있으면 → 변수
    • 바꿀 수 없으면 → 상수
  • 불변값 ≠ 상수
    • 변수와 상수를 구분 짓는 변경 가능성 대상 → 변수 영역 메모리
      • 한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당할 수 있는지 여부가 관건
    • 불변성 여부를 구분할 때의 변경 가능성 대상 → 데이터 영역 메모리

불변값

기본형 데이터는 모두 불변값이다.

  • 숫자, 문자열, boolean, null, undefined, Symbol

      var a = 'abc';
      a = a + 'def';  // 변수 a -> 기존 문자열이 변경 문자열로 바뀌는 것이 아니라, 새로운 문자열 데이터를 만들어 그 주소를 변수 a에 할당.
    
      var b = 5;  // 변수 b -> 컴퓨터는 데이터 영역에서 5를 찾고, 없으면 데이터 공간을 하나 만들어 저장, 그 주소를 변수 b에 할당.
      var c = 5;  // 변수 c -> 숫자 5가 이미 데이터 영역에서 존재하기 때문에, 숫자 5가 위치하는 주소를 변수 c에 할당.
      b = 7; // 변수 b -> 기존 저장된 5 자체를 7로 바꾸는 것이 아니라 기존에 저장했던 7을 찾아서 있으면 7의 주소값을 변수 b에 할당하고, 없으면 새로 만들어서 b에 할당
    
    • 변경은 새로 만드는 동작을 통해서만 이뤄진다. (= 불변값의 성질)
    • 한 번 만들어진 값은 [가비지 컬렉팅](https://ko.javascript.info/garbage-collection)을 당하지 않는 한 영원히 변하지 않는다.

가변값

참조형 데이터는 기본적인 성질은 가변값인 경우가 많다.

  • 설정에 따라 변경 불가능한 경우도 있고, 아예 불변값으로 활용하는 방안도 있다.

    • Object.defineProperty, Object.freeze

      // 참조형 데이터의 초기화
      
      var obj1 = {
        a: 1,
        b: 'bbb'
      };
      

      image.png

    • 변수 영역의 빈 메모리 공간(@1002)을 확보하고, 그 주소의 이름을 obj1로 지정한다.

    • 임의의 데이터 저장 메모리 공간(@5001)에 데이터를 저장하려고 보니 여러 개의 프로퍼티 로 이뤄진 데이터 그룹이다. 이 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역 을 마련하고, 그 영역의 주소(@7103 ~ ?)를 @5001에 저장한다.
    • @7103@7104에 각각 a와 b라는 프로퍼티 이름을 지정한다.
    • 데이터 영역에서 숫자 1을 검색한다. 검색 결과가 없으므로 임의의 @5003에 저장하고, 이 주소를 @7103에 저장한다. 마찬가지로 문자열 ‘bbb’ 역시 임의로 @5004에 저장하고, 이 주소를 @7104에 저장한다.

기본형 데이터와 참조형 데이터의 차이는 객체의 변수(프로퍼티) 영역 이 별도로 존재한다는 점이다.

  • 객체가 별도로 할애한 영역은 변수 영역일 뿐 데이터 영역은 기존의 메모리 공간을 그대로 공유하고 있다.

    • 데이터 영역에 저장된 값은 모두 불변값이다.
    • 그러나 변수에는 다른 값을 얼마든지 대입할 수 있다.
    • 이 부분 때문에 참조형 데이터는 불변(immutable)하지 않다.(= 가변값이다.)라고 하는 것이다.

      var obj1 = {
        a: 1,
        b: 'bbb'
      };
      
      obj1.a = 2  // 참조형 데이터 obj1의 a 프로퍼티 재할당
      

    image.png

    • 변수 obj1이 바라보고 있는 주소는 @5001로 변하지 않았다.
      • 즉, 새로운 객체 가 만들어진 것이 아니라, 기존의 객체 내부의 값만 바뀐 것이다.

중첩 객체(nested object) : 참조형 데이터의 프로퍼티에 다시 참조형 데이터가 할당된 객체

  • 다음 중첩 객체가 가정하자.

      var obj = {
          x: 3,
          arr: [ 3, 4, 5 ]
      };
    

    image.png

  • 이 상태에서 아래와 같이 프로퍼티를 재할당하면 다음과 같다.

      obj.arr = 'str';  // arr 프로퍼티의 값을 배열 [ 3, 4, 5 ] -> 문자열 "str"
    

image.png

변수 복사 비교

// 변수 복사

var a = 10;
var b = a;

var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

image.png

  • 변수를 복사하는 과정은 기본형 데이터와 참조형 데이터 모두 같은 주소를 바로보게 되는 점에서는 동일하다.
  • 복사 과정은 동일하지만 데이터 할당 과정에서는 차이가 있기 때문에 변수 복사 이후의 동작(like. 재할당)에도 큰 차이가 발생한다.
```jsx
var a = 10;
var b = a;

var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

b = 15;  // 변수 b는 15로
obj2.c = 20;  // 변수 obj2의 c 프로퍼티는 값을 20으로 재할당
```

image.png

  • 기본형 데이터를 복사한 변수 b의 값을 바꿨더니 @1002의 값은 달라졋다.

    • 변수 a와 b는 서로 다른 주소를 바라보게 됐다.
  • 반면, 참조형 데이터를 복사한 변수 obj2의 프로퍼티 값을 바꿨더니 @1004의 값은 달라지지 않았다.

    • 변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있는 상태다.
  • 즉, 다음과 같다.

          a !== b
          obj1 === obj2
    

    이 결과가 바로 기본형과 참조형 데이터의 가장 큰 차이점이다.

    • 어떤 데이터 타입이든(= 원시형 데이터든, 참조형 데이터든) 변수에 할당하기 위해서는 주소값을 복사해야 한다.
      • 즉, 엄밀히 따지면 자바스크립트의 모든 데이터 타입은 참조형 데이터일 수밖에 없다.
      • 다만, 원시형 데이터와 참조형 데이터의 차이는 다음과 같다.
        • 기본형 데이터 → 주소값을 복사하는 과정이 한 번만 이뤄진다.
        • 참조형 데이터 → 한 단계를 더 거치게 된다는 차이가 있을 뿐이다.

참조형 데이터의 프로퍼티 값 변경이 아닌, 객체 자체를 변경했을 경우

var a = 10;
var b = a;

var obj1 = { c: 10, d: 'ddd' };
var obj2 = obj1;

b = 15;
obj2 = { c: 20, d: 'ddd' };  // 변수 obj2에 새로운 객체를 재할당

image.png

  • 참조형 데이터가 가변값 이라고 설명할 때의 가변 은 참조형 데이터 자체를 변경할 경우가 아닌 내부의 프로퍼티를 변경할 때만 성립하는 말이다.

불변 객체

참조형 데이터의 가변 은 데이터 자체가 아닌 내부 프로젝트를 변경할 때만 성립한다. 그럼, 데이터 자체를 변경하고자하면, 기본형 데이터와 마찬가지로 기존 데이터는 변하지 않는다.

  • 그럼 참조형 데이터는 내부 프로퍼티를 변경할 필요가 있을 때마다 새로운 객체를 만들어 재할당하기로 규칙을 정하거나 자동으로 새로운 객체를 만드는 도구를 활용한다면 객체 역시 불변성을 확보할 수 있다.
    • 불변 객체로 취급하고, 그렇지 않은 경우에는 기존 방식대로 사용하는 식으로 상황에 따라 대처하면 된다.

언제 불변 객체가 필요할까 ?

  • 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우에 필요하다.

      // 객체 가변성에 따른 문제점
    
      var user = {
          name: 'Youngmin',
          age: 20
      };
    
      var changeName = function(user, newName) {
          var newUser = user;
          newUser.name = newName;
          return new User;
      };
    
      var user2 = changeName(user, 'Youngman');
    
      if(user !== user2) {
          console.log('유저 정보 변경');
      }
    
      console.log(user.name, user2.name);  // Youngman Youngman
      console.log(user === user2);  // true
    
      // 객체 가변성에 따른 문제점 해결 방법
    
      var user = {
          name: 'Youngmin',
          age: 20
      };
    
      var changeName = function(user, newName) {
          return {
              name: newName,
              age: user.age,
          }
      };
    
      var user2 = changeName(user, 'Youngman');
    
      if(user !== user2) {
          console.log('유저 정보 변경');  // 유저 정보 변경
      }
    
      console.log(user.name, user2.name);  // Youngmin Youngman
      console.log(user === user2);  // false
    
    • changeName 함수는 이제 새로운 객체를 반환한다.

      • user, user2 는 완전히 서로 다른 객체이므로 안전하게 변경 전후를 비교할 수 있다.

      그런데, 새로운 객체를 반환할 때 변경할 필요가 없는 기존 객체의 프로퍼티(age)를 굳이 넣어줄 필요가 있는가 ? 만약 객체의 프로퍼티가 1000개라면 1000개를 전부 넣어주는 것은 힘들 것이다.

    • 이를 위해, 대상 객체의 프로퍼티 개수에 상관 없이 모든 프로퍼티를 복사하는 함수를 만드는 것이 좋은 방법 중 하나.

        var shallowCopy = function(target) {
            var result = {};
            for(var prop in target) {
                result[prop] = target[prop];  // [key]: value 형태로 새로운 객체를 생성
            }
            return result;
        }
      

얕은 복사와 깊은 복사

  • 얕은 복사(shallow copy) : depth 가 1인 값만 복사하는 방법
  • 깊은 복사(deep copy) : depth 의 상관없이 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법

얕은 복사는 중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사할 때 주소값만 복사

  • 이 경우, 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터의 주소를 가리키게 된다.
  • 따라서, 사본의 값을 변경하면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀌는 현상이 발생한다.

      // 중첩된 객체(nested object)에 대한 얕은 복사(shallow copy) 예
    
      var user = {
          name: 'Youngmin',
          profile: {
              age: 20,
          }
      };
    
      var user2 = shallowCopy(user);  // shallowCopy -> 객체에 대해 얕은 복사로 새로운 객체를 반환하는 함수
    
      user2.name = 'Youngman';
      console.log(user.name === user2.name);  // false << name 프로퍼티는 기본형 데이터이며, 기본형 데이터는 값에 대한 메모리가 새로 생성되기 때문
    
      user.profile.age = 10;
      console.log(user.profile.age === user2.profile.age);  // true  << profile 프로퍼티는 참조형 데이터이며, 얕은 복사를 했음에도 기존 데이터를 그대로 참조 중이기 때문에 user, user2 객체 둘다 같은 profile 프로퍼티를 바라보고 있게 된다.
    

깊은 복사를 통해 완전히 불변 객체로 만들수 있다.

// 중첩된 객체에 대한 깊은 복사(deep copy) 예

...

var user2 = deepCopy(user);

user.profile.age = 10;
console.log(user.profile.age === user2.profile.age);  // false << user.profile 프로퍼티와 user2.profile 프로퍼티는 완전히 다른 참조형 데이터 주소값을 가지기 때문이다.

따라서, 어떤 객체를 복사할 때 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때 객체의 프로퍼티 중에서 그 값이 기본형 데이터일 경우에는 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 모든 프로퍼티들을 복사해야 한다.

  • 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야만 비로소 깊은 복사(deep copy)가 되는 것이다.

      // 객체의 깊은 복사를 수행하는 범용 함수
    
      var deepCopyObject = function(target) {
          var result = {};
    
          if(typeof target === 'object' && target !== null) {  // target !== null 은 typeof null 의 결과도 'object' 이기 때문(자바스크립트 언어 버그)
              for(var prop in target) {
                  result[prop] = deepCopyObject(target[prop]);
              }
          } else {
              result = target;
          }
    
          return result;
      }
    
      // deepCopyObject 함수 테스트
    
      var obj = {
          a: 1,
          b: {
              c: null,
              d: [1, 2]
          }
      };
    
      var obj2 = deepCopyObject(obj);
    
      obj2.a = 3;
      obj2.b.c = 4;
      obj.b.d[1] = 3;
    
      console.log(obj);  // { a: 1, b: { c: null, d: [ 1, 3 ] } }
      console.log(obj2);  // { a: 3, b: { c: 4, d: { '0': 1, '1': 2 } } }
    
  • hasOwnProperty 메서드를 활용해 프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게끔 할 수도 있다.

  • ES5의 getter/setter 를 복사하는 방법은 ES6의 Object.getOwnPropertyDescriptor 또는 ES2017 의 Object.getOwnPropertyDescriptors 외에는 마땅한 방법이 없다.

더 간단하게 깊은 복사를 처리할 수 있는 다른 방법

객체를 JSON 문법으로 표현된 문자열로 변환 → 다시 JSON 객체로 바꾸는 것

var deepCopyObjectViaJSON = function(target) {
    return JSON.parse(JSON.stringify(target));
};  // 메서드나 숨겨진 프로퍼티를 제외한 객체의 모든 프로퍼티를 복사해주는 함수

var obj = {
    a: 1,
    b: {
        c: null,
        d: [1, 2],
        func1: function() { console.log(3); },
        func2: function() { console.log(4); }
};

var obj2 = deepCopyObjectViaJSON(obj);

obj2.a = 3;
obj2.b.c = 4;
obj.b.d[1] = 3;

console.log(obj);  // { a: 1, b: { c: null, d: [ 1, 3 ], func1: [Function: func1], func2: [Function: func2 } }
console.log(obj2);  // { a: 3, b: { c: 4, d: [ 1, 2 ] } }
  • 다만, 메서드(함수)나 숨겨진 프로퍼티인 __proto__getter/setter 등과 같이 JSON으로 변경할 수 없는 프로퍼티들은 모두 무시한다.
  • HTTPRequest로 받은 데이터를 저장한 객체를 복사할 때 등 순수한 정보만 다룰 때 활용하기 좋은 방법이다.

undefined와 null

자바스크립트에서 없음 을 나타내는 값이 두 가지 있다. 바로 undefined , null

  • 하지만, 미세하게 다르고 사용하는 목적 또한 다르다.

undefined

  • 사용자가 명시적으로 할당할 수도 있지만, 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여하는 경우도 있다.

    • 자바스크립트 엔진이 자동으로 부여하는 경우

      자바스크립트 엔진은 사용자가 어떤 값을 지정할 것이라고 예상되는 상황임에도 실제로는 그렇게 하지 않았을 때 undefined 를 반환한다.

      1. 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
      2. 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
      3. return 문이 없거나 호출되지 않는 함수의 실행 결과
      // 자동으로 undefined 를 부여하는 경우
      
      var a;
      console.log(a);  // undefined -> 1. 값을 대입하지 않은 변수에 접근 시
      
      var obj = { a: 1 };
      console.log(obj.a);  // 1
      console.log(obj.b);  // undefined -> 2. 존재하지 않은 프로퍼티에 접근 시
      console.log(b);  // ReferenceError: b is not defined
      
      var func = function() { };
      var c = func();  // 3. 반환(return)값이 없으면 암묵적으로 undefined 를 반환
      console.log(c);  // undefined
      
      // undefined와 배열
      
      var arr1 = [];
      arr1.length = 3;
      console.log(arr1);  // [empty x 3] -> 배열에 3개의 빈 요소를 확보만 했고, 각 요소에 어떤 값도 심지어 undefined 조차도 할당돼 있지 않음을 의미
      
      var arr2 = new Array(3);
      console.log(arr2);  // [empty x 3] -> 크기가 3인 빈 배열 인스턴스 의미
      
      var arr3 = [undefined, undefined, undefined];
      console.log(arr3);  // [undefined, undefined, undefined] -> 리터럴 방식으로 배열을 생성하면서 각 요소에 undefined 를 명시적으로 부여한 것
      

      결과에서 말하고 싶은 것은 비어있는 요소undefined 를 할당한 요소 는 엄연히 다른 것이다.

    • 비어있는 요소 는 순회와 관련된 여러 배열 메서드들의 순회 대상에서 제외된다.

        var arr1 = [undefined, 1];
        var arr2 = [];
        arr2[1] = 1;
      
        arr1.forEach(function(v, i) { console.log(v, i); });
        // undefined 0
        // 1 1
        arr2.forEach(function(v, i) { console.log(v, i); });
        // 1 1
      
        arr1.map(function(v, i) { return v + i });
        // [NaN, 2]
        arr2.map(function(v, i) { return v + i });
        // [empty, 2]
      
        arr1.filter(function(v) { return !v; });
        // [undefined]
        arr2.filter(function(v) { return !v; });
        // []
      
        arr1.reduce(function(p, c, i) { return p + c + i; }, '');
        // undefined 011
        arr2.reduce(function(p, c, i) { return p + c + i; }, '');
        // 11
      
      • undefined 를 할당한 배열에 대해서는 일반적으로 알고 있는 배열 메서드들이 모드 요소를 순회해서 결과를 출력한다.
      • 그러나, 비어있는 요소가 있는 배열의 경우 비어있는 요소에 대해서는 어떠한 처리도 하지 않고 건너뛰었음을 확인할 수 있다.

      이제, 사용자가 명시적으로 부여한 경우와 비어있는 요소에 접근하려 할 때 반환되는 두 경우의 undefined 의 의미를 구분할 수 있다.

    • 명시적으로 undefined 를 부여한 경우

      • undefined 가 비록 “비어있음” 의 의미하긴 하지만 하나의 값으로 동작하기 때문에 이때의 프로퍼티나 배열의 요소는 고유한 키값(프로퍼티 이름)이 실존하게 되고, 따라서 순회의 대상이 될 수 있다.
    • 사용자가 아무것도 하지 않은 채로 접근했을 때 자바스크립트 엔진이 하는 수 없이 undefined 를 반환해준 경우
      • 해당 프로퍼티 내지 배열의 키값(인덱스) 자체가 존재하지 않음을 의미한다.
      • 값으로써 어딘가에 할당된 undefined 는 실존하는 데이터인 반면, 자바스크립트 엔진이 반환해주는 undefined 는 문자 그대로 값이 없음을 나타낸다.

TDZ(Temporary Dead Zone) : ES6에 등장한 const, let 변수는 undefined 를 할당하지 않은 채로 초기화를 마치며, 이후 특정 값을 할당하기 전까지는 해당 변수에 접근할 수 없다.

image.png

null

명확하게 값이 비어있음 을 나타내고 싶을 때 사용

  • 이 의미를 잘 지킨다면, undefined 는 오직 값을 대입하지 않은 변수에 접근하고자 할 때 자바스크립트 엔진이 암묵적으로 반혼해주는 값 으로서만 존재할 수 있다.
  • typeof null === object 이다.

    • 이것은 자바스크립트 자체 버그다.

        // 변수가 null 인지 여부를 판단하는 방법
      
        var n = null;
        console.log(typeof n);  // object
      
        // 동등 연산자(equality operator) (==) 로 비교할 경우 << 💩
        console.log(n == undefined);  // true
        console.log(n == null);  // true
      
        // 일치 연산자(identity operator) (===) 로 비교할 경우 << 👍
        console.log(n === undefined);  // false
        console.log(n === null);  // true
      

정리

  • 자바스크립트 데이터 타입에는 크게 기본형 데이터참조형 데이터 가 있다.
    • 기본형 데이터는 불변값(immutable) 참조형 데이터는 가변값(mutable)
  • 변수(variable)는 변경 가능한 데이터가 담길 수 있는 공간. 식별자(identifier)는 그 변수의 이름.
  • 변수 선언
    • 컴퓨터는 우선 메모리의 빈 공간(= 변수 영역)에 식별자를 저장하고 그 공간에 자동으로 undefined 를 할당
    • 이후 그 변수에 기본형 데이터를 할당하려고 하면 별도의 공간(= 데이터 영역)에 데이터를 저장하고 그 공간의 주소를 변수의 값 영역에 할당
  • 참조형 데이터를 할당
    • 컴퓨터는 참조형 데이터 내부 프로퍼티들을 위한 변수 영역을 별도로 확보
    • 확보된 주소를 변수에 연결하고, 다시 앞서 확보한 변수 영역에 각 프로퍼티의 식별자를 저장하고, 각 데이터를 별도의 공간에 저장해서 그 주소를 식별자들과 매칭시킨다.
    • 할당 과정에서 기본형 데이터와 참조형 데이터의 차이가 생긴 이유는 참조형 데이터가 여러 개의 프로퍼티(변수)를 모은 그룹 이기 때문
    • 이 차이로 인해 참조형 데이터를 가변값 으로 여겨야만 하는 상황이 발생한다.
  • 참조형 데이터를 가변값으로 여겨야 하는 상황임에도 이를 불변값으로 사용하는 방법
    • 내부 프로퍼티를 일일이 복사하는 메서드를 생성(deep copy)
    • 라이브러리(immer.js, immutable.js, immutability-helper)
    • js 메서드(spread operator, object.assign) → 얕은 복사(shallow copy)
  • 없음 을 나타내는 두 가지 → undefined, null

    • undefined → 어떤 변수에 값이 존재하지 않을 경우
    • null → 사용자가 명시적으로 “없음"을 표현하고 싶은 경우 직접 대입한 값

      사용자가 없음을 표현하기 위해 명시적으로 undefined 를 대입하는 것은 지양하는 것이 좋다.


참조