[JS] 데이터 타입

Haegu·2023년 8월 25일

1. 데이터 타입의 종류

데이터 타입타입하위분류차이
기본형(원시형)
primitive type
불변성
immutability
숫자 number
문자열 string
불리언 boolean
null
undefined
심볼 Symbol
깂이 담긴 주솟값
바로 복제
참조형
reference type
객체 object배열 Array
함수 Function
날짜 Date
정규표현식 RegExp
Map, WeakMap
Set, WeakSet
값이 담긴 주솟값들로
이루어진 묶음을
가리키는 주솟값을 복제



2. 데이터 타입에 관한 배경지식

메모리 영역에서 자바스크립트 데이터가 처리되는 과정, 식별자, 변수

메모리와 데이터

  • 비트 bit : 0 또는 1만 표현할 수 있는 하나의 메모리 조각

    • 각 비트는 고유한 식별자를 통해 위치 확인
    • 비트 단위로 위치 확인 -> 비효율적
  • 바이트 byte : 검색 시간을 줄이고 표현할 수 있는 값이 늘어난 공간

    • 각 바이트는 시작하는 비트의 식별자로 위치 확인
    • 1 바이트 = 8 비트 = 256 (2⁸) 개의 값 표현
    • 2 바이트 = 16 비트 = 65536 (2¹⁶) 개의 값 표현

모든 데이터는...

  • 메모리 주솟값으로 서로 구분 및 연결
    • = 바이트 단위의 식별자

  • 동적 타입 언어 (JavaScript, Python, PHP)

    • 변수의 타입이 런타임에 결정됨
    • 사용자가 직접 형변환 할 필요 X
      • 숫자의 경우 정수형인지 부동소수형인지 구분 않고 8바이트 확보
  • 정적 타입 언어 (TypeScript, Java, C++)

    • 변수의 타입이 컴파일 타임에 결정됨
    • 메모리의 낭비를 최소화 하기 위해
      데이터 타입별로 할당할 메모리 영역 정해둠
    • 사용자가 직접 형변환 해야 함

식별자와 변수

  • 변수 : 변할 수 있는 데이터가 담길 수 있는 공간
  • 식별자 : 변수명 = 어떤 데이터를 식별하는 데 사용하는 이름



3. 변수 선언과 데이터 할당

변수 선언

var a;

< 변수 선언에 대한 메모리 영역의 변화 >


데이터 할당

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

var a = 'abc'; // 변수 선언과 할당

< 데이터 할당에 대한 메모리 영역의 변화 >

  1. 변수 영역에서 빈 공간 (@1003) 확보
  2. 확보 공간의 식별자를 a로 지정
  3. 데이터의 빈 공간 (@5004)에 문자열 'abc' 저장
  4. 변수 영역에서 a라는 식별자 검색 (@1003)
  5. 앞서 저장한 문자열의 주소 (@5004)를 @1003의 공간에 대입

Q. 변수 영역에 값을 직접 대입하지 않고, 변수와 데이터를 별도의 공간에 나누어 저장하는 이유?

A. 메모리 효율적으로 관리하기 위함

  • 중복된 데이터에 대한 처리 효율성
    • 여러 개의 변수 생성해서 모든 변수에 같은 값을 할당하는 상황이면
      • 각 변수 공간마다 매번 할당 시
        : 변수 개수 x 할당할 값 메모리 용량 (500*8)
      • 할당할 값을 별도의 공간에 한 번만 저장하고 해당 주소만 입력 시
        : 변수 개수 x 주소 공간 메모리 용량 + 할당할 값 메모리 용량 (500*2+8)
  • 데이터 변환에 대한 처리 효율성
    • 미리 확보한 공간 내에서만 데이터 변환을 할 수 있다면
      변환한 데이터를 다시 저장하기 위해서는
      확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업 선행되어야 함
      • 메모리 상 중간에 있는 데이터를 늘려야 하면
        해당 공간보다 뒤에 저장된 데이터들을 전부 뒤로 옮기고
        이동시킨 주소를 각 식별자에 다시 연결해야 함
        -> 컴퓨터가 처리해야 할 연산 많아짐
      • 숫자형 데이터는 8바이트의 공간을 확보하지만
        문자열은 메모리 용량이 가변적 (한글, 영어, 글자수)
    • 기존 문자열에 어떤 변환을 하든
      원래 변수 데이터의 값이 저장된 공간에 새로운 값을 할당하는 대신
      무조건 문자열을 새로 만들어 별도의 공간에 저장 후
      그 데이터 주소를 변수 공간에 저장



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

변수 variable : 변수 영역 메모리 변경 가능성 있음
상수 constant : 변수 영역 메모리 변경 가능성 없음

변수 영역 메모리 변경 가능성 : 한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당 할 수 있는지 여부

불변값 : 데이터 영역 메모리 변경 가능성 없음

  • 기본형 데이터(숫자, 문자열, boolean, null, undefined, Symbol)

  • 불변값 성질 : 변경은 새로 만드는 동작을 통해서만 이뤄짐

    • 한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 한 영원히 변하지 않음
  • 불변성 예시 1

    var a = 'abc';
    a = a + 'def';
    • 기존의 'abc'가 'abcdef'로 바뀌는 것이 아닌
      새로운 문자열 'abcdef' 만들어 그 주소를 변수 a에 저장
  • 불변성 예시 2

    var b = 5;
    var c = 5;
    b = 7;
    • 변수 b에 숫자 5를 할당할 때
      데이터 영역에서 5를 찾고
      없으면 데이터 공간을 하나 만들어 저장한 후 그 주소를 b에 저장
    • 변수 c에 숫자 5를 할당할 때
      데이터 영역에서 5를 찾고
      이미 만들어 놓은 값이 있으니 그 주소 재활용
    • 변수 b의 값을 7로 바꿀 때
      데이터 영역에서 7을 찾고
      있으면 기존에 저장된 7의 주소 재활용
      없으면 데이터 공간을 하나 만들어 저장한 후 그 주소 b에 저장

가변값 : 데이터 영역 메모리 변경 가능성 있음

  • 참조형 데이터

    • 가변값 : 참조형 데이터 내부의 프로퍼티를 변경할 때만

    • 불변값

    • 기본형 데이터와의 차이

      • 객체의 변수(프로퍼티) 영역이 별도로 존재
        • 객체가 별도로 할애한 영역은 변수 영역
        • 데이터 영역은 기존의 메모리 공간 그대로 활용
        • 데이터 영역에 저장된 값은 모두 불변값
        • 변수에는 다른 값 대입 가능 -> 가변값

  • 참조형 데이터(객체)의 할당

    var obj1 = {
      a: 1,
      b: 'bbb',
    };

    1. 변수 영역의 빈 공간 (@1002) 확보, 그 주소 이름을 obj1로 지정
    2. 임의의 데이터 저장 공간 (@5001)에 데이터 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103 ~?)를 @5001에 저장
    3. @7103 및 @7104에 각각 a와 b라는 프로퍼티 이름 지정
    4. 데이터 영역에서 숫자 1 검색
      검색 결과 없으면 임의로 @5003에 저장, 이 주소를 @7103에 저장
      문자열 'bbb' 임의로 @5004에 저장, 이 주소를 @7104에 저장

  • 참조형 데이터(객체)의 프로퍼티 재할당
    var obj1 = {
      a: 1,
      b: 'bbb',
    };
    obj1.a = 2;
    1. 데이터 영역에서 숫자 2 검색
    2. 검색 결과 없으면 빈 공간인 @5005에 저장, 이 주소 @7103에 저장
    3. 변수 obj1이 바라보고 있는 주소는 @5001로 변하지 않음
      => 새로운 객체가 만들어진 것 X, 기존의 객체 내부의 값만 바뀜

  • 중첩된 참조형 데이터(중첩 객체)의 프로퍼티 할당

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

    1. 변수 영역의 빈 공간 (@1002) 확보, 그 주소 이름 obj로 지정
    2. 임의의 데이터 저장공간 (@5001)에 데이터 그룹의 각 프로퍼티들을 저장하기 위해 별도의 변수 영역 마련 (@7103 ~ ?), 그 영역의 주소를 @5001에 저장
    3. @7103에 이름 x를, @7104에 이름 arr 지정
    4. 데이터 영역에서 숫자 3 검색, 없으면 임의로 @5002에 저장, 이 주소 @7103에 저장
    5. @7104에 저장할 값은 배열로 데이터 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역 마련 (@8104 ~ ?), 그 영역의 주소 정보 (@8104 ~ ?)를 @5003에 저장한 다음, @5003을 @7104에 어장
    6. 배열의 요소 총 3개이므로 3개의 변수 공간 확보, 각각 인덱스 부여(0, 1, 2)
    7. 데이터 영역에서 숫자 3 검색 (@5002), 그 주소 @8104에 저장
    8. 데이터 영역에 숫자 4 없으면 @5004에 저장, 이 주소 @8105에 저장
    9. 데이터 영역에 숫자 5 없으면 @5005에 저장, 이 주소 @8106에 저장

  • obj.arr[1] 검색

    @1002 -> @5001 -> (@7103 ~ ?) -> @710 4 -> @5003 -> (@8104 ~ ?) -> @8105 -> @5004 -> 4 반환

  1. obj 검색 1: obj라는 식별자를 가진 주소 찾기 (@1002)

  2. obj 검색 2: 값이 주소이므로 그 주소로 이동 (@5001)

  3. obj 검색 3: 값이 주소이므로 그 주소로 이동 (@7103 ~ ?)

  4. obj.arr 검색 1: arr이라는 식별자를 가진 주소 찾기 (@7104)

  5. obj.arr 검색 2: 값이 주소이므로 그 주소로 이동 (@5003)

  6. obj.arr 검색 3: 값이 주소이므로 그 주소로 이동 (@8104 ~ ?)

  7. obj.arr[1] 검색 1: 인덱스 1에 해당하는 주소 찾기 (@8105)

  8. obj.arr[1] 검색 2: 값이 주소이므로 그 주소로 이동 (@5004)

  9. obj.arr[1] 검색 3: 값이 숫자형 데이터이므로 4 반환


  • obj.arr='str'; 재할당
  1. @5006에 문자열 'str' 저장, 그 주소 @7104에 저장

  2. @5003은 더이상 자신의 주소를 참조하는 변수 없음
    @5003의 참조 카운트는 @7104에 @5003이 저장돼 있던 시점까지는 1이었다가 @7104에 @5006이 저장되는 순간 0이 됨
    참조 카운트가 0인 메모리 주소는 가비지 컬렉터 GC 의 수거 대상 됨
    수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 됨
    @5003에 담겨있던 @8104 ~ ? 값도 사라지므로 연쇄적으로 각 데이터들의 참조 카운트가 0이 되고 GC 수거 대상이 되어 함께 사라짐

    참조 카운트 : 사진의 주로를 참조하는 변수의 개수
    가비지 컬렉터 : 런타임 환경에 따라 특정 시점이나 메모리 사용량이 포화 상태에 임박할 때 마다 자동으로 수거 대상들을 수거함

  • 변수 복사

    • 기본형 데이터
      var a = 10;
      var b = a;
    • 참조형 데이터
      var obj1 = { c: 10, d: 'ddd' };
      var obj2 = obj1;
      동작 과정은 같음

  • 변수 복사 이후 값 변경 결과 비교 1 - 객체의 프로퍼티 변경 시
var a = 10;
var b = a;

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

b = 15;
obj2.c = 20;

변수 a와 b는 서로 다른 주소,
변수 obj1와 obj2는 같은 객체 바라봄
=> 참조형 데이터 내부의 프로퍼티를 변경할 때만 가변값

a !== b
obj1 === obj2

모든 데이터 타입은 참조형 데이터일 수 밖에 없지만
기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고
참조형은 한 단계를 더 거침


  • 변수 복사 이후 값 변경 결과 비교 2 - 객체 자체 변경 시
var a = 10;
var b = a;

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

b = 15;
obj2 = { c: 20, d: 'ddd' };



5. 불변 객체

불변 객체가 필요한 상황
: 값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우

  • 객체의 가변성에 따른 문제점

    • 정보가 바뀐 시점에 알림 보내기
    • 바뀌기 전 후 정보의 차이를 가시적으로 보여줘야하는 등
    • 의 기능 구현 시 변경 전 후에 서로 다른 객체여야 함
    var user = {
      name: 'Jaenam',
      gender: 'male'
    };
    
    var changeName = function (user, newName) {
      var newUser = user;
      newUser.name = newName;
      return newUser;
    };
    
    var user2 = changeName(user, 'Jung');
    
    if (user !== user2) {
      console.log('유저 정보가 변경되었습니다.');
    }
    
    console.log(user.name, user2.name); // Jung Jung
    console.log(user === user2);		// true

  • 객체의 가변성에 따른 문제점의 해결 방법

    • 새로운 객체를 반환하도록 수정됨
    • 그럼에도 불구하고! 변경할 필요가 없는 기존 객체의 프로퍼티(gender)를 하드 코딩
      => 대상 객체의 프로퍼티 개수에 상관 없이 모든 프로퍼티를 복사하는 함수를 만들자!
    var user = {
      name: 'Jaenam',
      gender: 'male'
    };
    
    var changeName = function (user, newName) {
      return {
        name: newName,
        gender: user.gender
      };
    };
    
    var user2 = changeName(user, 'Jung');
    
    if (user !== user2) {
      console.log('유저 정보가 변경되었습니다.'); // 유저 정보가 변경되었습니다.
    }
    
    console.log(user.name, user2.name);	    // Jaenam Jung
    console.log(user === user2);				// false

  • 기존 정보를 복사해서 새로운 객체를 반환하는 함수 (얕은 복사)

    • user 불변 객체

      • user 객체 내부의 변경이 필요할 때 무조건 copyObject 함수(얕은 복사만 수행) 사용 전제
      • => 시스템적으로 제약 걸기 : immutable.js, baobab.js 등 라이브러리
      var copyObject = function (target) {
        var result = {};
        for (var prop in target) {
          result[prop] = target[prop];
        }
        return result;
      };
      
      var user = {
        name: 'Jaenam',
        gender: 'male'
      };
      
      var user2 = copyObject(user);
      user2.name = 'Jung';
      
      if (user !== user2) {
        console.log('유저 정보가 변경되었습니다.'); // 유저 정보가 변경되었습니다.
      }
      
      console.log(user.name, user2.name);	     // Jaenam Jung
      console.log(user === user2);			 // false

얕은 복사와 깊은 복사

  • 얕은 복사 shallow copy : 바로 아래 단계의 값만 복사

    • 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사
    • 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터의 주소 가짐
    • 사본 바꾸면 -> 원본도 바뀜
    • 원본 바꾸면 -> 사본도 바뀜
    • 객체에 직접 속한 프로퍼티들은 복사 시 완전히 새로운 데이터가 만들어짐
    • 한 단계 더 들어간 내부 프로퍼티들은 기존 데이터를 그대로 참조
      => 깊은 복사로 내부 프로퍼티들도 불변 객체로 만들자
  • 깊은 복사 deep copy : 내부의 모든 값들을 하나하나 찾아서 전부 복사

    • 기본형 데이터 : 그대로 복사
    • 참조형 데이터 : 다시 그 내부의 프로퍼티들을 복사

  • 중첩된 객체에 대한 얕은 복사

    var user = {
      name: 'Jaename',
      urls: {
        portfolio: 'http://github.com/abc',
        blog: 'http://blog.com',
        facebook: 'http://facebook.com/abc'
      }
    };
    var user2 = copyObject(user);
    
    user2.name = 'Jung';
    console.log(user.name === user2.name);	// false
    
    user.urls.porfolio = 'http://portfolio.com';
    console.log(user.urls.portfolio === user2.urls.portfolio);	// true
    
    user.urls.blog = '';
    console.log(user.urls.blog === user2.urls.blog);			// true

  • 중첩된 객체에 대한 깊은 복사

    var user2 = copyObject(user);
    user2.urls = copyObject(user.urls);
    
    user.urls.portfolio = 'http://portfolio.com';
    console.log(user.urls.portfolio === user2.urls.portfolio);	// false
    
    user2.urls.blog = '';
    console.log(user.urls.blog === user2.urls.blog);			// false
  • 객체의 깊은 복사를 수행하는 범용 함수

    • 내부 프로퍼티 순회하며 copyObjectDeep함수 재귀적 호출
    var copyObjectDeep = function(target) {
      var result = {};
      if (typeof target === 'object' && target !== null) {
        for (var prop in target) {
          result[prop] = copyObjectDeep(target[prop]);
        }
      } else {
        result = target;
      }
      return result;
    };
  • 깊은 복사 결과 확인

    var obj = {
      a: 1,
      b: {
        c: null,
        d: [1, 2]
      }
    };
    var obj2 = copyObjectDeep(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} } }
  • JSON을 활용한 간단한 깊은 복사

    • 객체를 JSON 문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꿈
    • 단! 메서드(함수), 숨겨진 프로퍼티__proto__, getter/setter 같은 JSON으로 변경할 수 없는 프로퍼티 무시함
    var copyObjectVialJSON = 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 = copyObjectVialJSON(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: f() }, func2: f() }
    console.log(obj2);  // { a: 3, b: { c: 4, d: [1, 2] } }



6. undefined와 null

  • undefined 값이 없음

    • 사용자가 명시적으로 지정할 수는 있음 (null이 있으니 굳이?)

    • 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여

      • 값을 대입하지 않은 변수 === 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때
        자바스크립트가 직접 undefined 할당
        var 변수는 LE가 활성화될 때 생성되면서 동시에 undefined로 초기화됨
        let, const 변수는 LE가 활성화 될 때 생성되지만 undefined를 할당하지 않은 채 초기화됨
        let, const 변수 -> 특정 값 할당 전까지 접근 불가

        var a;
        console.log(a);	// 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]
        • 빈 요소와 배열의 순회
          unedfined 그 자체로 값 === 고유의 키 값(프로퍼티 이름) 실존 => 순환의 대상
          아무것도 하지 않고 접근 === 고유의 키 값(프로퍼티) 존재 X

          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; }, '');  // undefined011
          arr2.reduce(function (p, c, i) { return p + c + i; }, '');  // 11
      • 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때

        var obj = { a: 1 };
        console.log(obj.a); // 1
        console.log(obj.b); // undefined
        console.log(b);     // 선언되지 않은 변수 접근 c.f) ReferenceError: b is not defined
      • return 문이 없거나 호출되지 않는 함수의 실행 결과

        var func = function() { };
        var c = func();	// 반환 return 값이 없으면 undefined 반환
        console.log(c);	// undefined
  • null 비어있음

    • 비어있음을 명시적으로 나타내고 싶을 때

    • 주의! typeof nullobject라는 자바스크립트 자체 버그

    • 변수의 값이 null인지 판별 위해 일치 연산자 identity operator

      var n = null;
      console.log(typeof n);		// object
      
      console.log(n == undefined);  // true 동등 연산자
      console.log(n == null);		// true
      
      console.log(n === undefined); // false 일치 연산자
      console.log(n === null);		// true



*코어자바스크립트 01 데이터 타입 정리



profile
사용자 경험을 위해 모험을 떠나는 해구

0개의 댓글