새오의 개발 기록

코어 자바스크립트: 1. 데이터 타입 본문

Javascript/코어 자바스크립트

코어 자바스크립트: 1. 데이터 타입

새오: 2022. 12. 21. 23:50

 

 

01. 데이터 타입

 

1. 데이터 타입의 종류

 

자바스크립트의 데이터 타입은 크게 두 가지로 나뉨

 

데이터 타입의 종류

  1. 기본형
    • Number
    • String
    • Boolean
    • null
    • undefined
    • Symbol(es6에 추가)
  2. 참조형
    • object
      • Array
      • Function
      • Date
      • RegExp
      • Map, WeakMap
      • Set, WeakSet

 

데이터 타입의 특징

  1. 기본형
    • 할당/연산 시 복제된다.
    • 값이 담긴 주솟값을 바로 복제
    • 불변성(immutability)를 띈다.
  2. 참조형
    • 할당/연산 시 참조된다.
    • 값이 담긴 주솟값들로 이루어진 묶음을 가리키는 주솟값을 복제

 

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

 

메모리와 데이터

  • 1바이트는 8개의 비트로 구성, 1비트는 0 또는 1의 두 가지 값을 표현
  • 모든 데이터는 바이트 단위의 식별자, 즉 '메모리 주솟값'을 통해 서로 구분하고 연결

 

식별자와 변수

  • 변수: variable. 변할 수 있는 데이터
  • 식별자: 어떤 데이터를 식별하는 데 사용하는 이름. 즉 변수명을 의미

 

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

 

변수 선언

var a; // 변할 수 있는 데이터를 만든다. 이 데이터의 식별자는 a 로 한다.

 

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

주소 ... 1002 1003 1004 1005 ...
데이터     이름: a
값:
     
  • 메모리에서 비어있는 공간 하나를 확보한다.(1003번)
  • 이 공간의 이름(식별자)를 a 라고 지정한다.

 

데이터 할당

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

var a = 'abc'; // 변수 선언과 할당을 한 문장으로 표현

 

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


변수 영역
주소 ... 1002 1003 1004 1005 ...
데이터     이름: a
값: @5004
     

데이터 영역
주소   5002 5003 5004 5005 ...
데이터       'abc'    
  • 변수 영역 메모리에서 비어있는 공간 하나를 확보한다.(1003번)
  • 이 공간의 이름(식별자)를 a 라고 지정한다.
  • 데이터 영역의 빈 공간(@5004)에 문자열 'abc'를 저장한다.
  • 변수 영역에서 a라는 식별자를 검색한다(@1003)
  • 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입한다.

 

변수 영역에 값을 직접 대입하지 않고 굳이 한 단계를 더 거치는 이유는?

  • 데이터 변환을 자유롭게 할 수 있게 함과 동시에 메모리를 더 효율적으로 관리
  • 자바스크립트는 숫자형 데이터에 64비트(8바이트)의 공간을 확보하는데 문자열은 정해진 규격이 없음. 메모리 용량이 가변적이므로 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 필요 없음
  • 변수와 데이터를 별도의 공간에 나누어 저장하면 효율적으로 문자열 데이터의 변환을 처리할 수 있음
    • 문자열 'abc'의 마지막에 'def'를 추가하라고 하면 컴퓨터는 앞서 'abc'가 저장된 공간에 'abcdef'를 할당하는 대신 'abcdef'라는 문자열을 새로 만들어 별도의 공간에 저장하고 그 주소를 변수 공간에 연결함

변수 영역
주소 ... 1002 1003 1004 1005 ...
데이터     이름: a
값: @5005
     

데이터 영역
주소   5002 5003 5004 5005 ...
데이터       'abc' 'abcdef'  

 

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

 

불변값

var a = 'abc'; // 변수 a에 문자열 'abc' 할당
a = a + 'def'; // 변수 a에 문자열 'abcdef' 재할당(데이터 영역에 새로 만들어 할당함)

var b = 5; // 변수 b에 숫자 5 할당(데이터 영역에서 5를 찾고 없으면 데이터 공간을 만들어 저장)
var c = 5; // 4번째 줄에서 이미 데이터 영역에 5 만들어 놨으므로 재활용 해서 할당
b = 7; // 데이터 공간에 7은 없으므로 새로 만들어서 재할당
  • 변수와 상수를 구분하는 성질은 변경 가능성
    • 변경 가능성의 대상은 변수 영역 메모리. 한 번 데이터 할당이 이뤄진 변수 공간에 다른 데이터를 재할당할 수 있는지 여부가 관건
  • 불변값과 상수는 같은 개념이 아님
    • 불변성 여부를 구분할 때의 변경 가능성의 대상은 데이터 영역 메모리
    • 예시에서 2번째 줄에서 한 번 만든 값을 변경하는게 아니라 데이터 영역에 새로 만든 이유가 불변값의 성질 때문임
    • 한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 이상 영원히 변하지 않음. 
    • 새로 만드는 동작을 통해서만 변경 가능
  • 기본형 데이터(Number, String, boolean, null, undefined, Symbol)은 모두 불변값

 

가변값

  • 참조형 데이터는 기본적으로 가변값인 경우가 많음

 

참조형 데이터의 할당

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

변수 영역
주소 1001 1002 1003 1004
데이터   이름: obj1
값: @5001
   

데이터 영역
주소 5001 5002 5003 5004
데이터 @7103 ~ ?   1 'bbb'

객체 @5001의
변수 영역
주소 7103 7104 7105 7106
데이터 이름: a
값: @5003
이름: b
값: @5004
 

  • 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj1로 정합니다.
  • 임의의 데이터 저장 공간(@5001)에 데이터를 저장하려고 보니 여러 개의 프로퍼티로 이뤄진 데이터 그룹이라 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103 ~ ?)을 @5001에 저장
  • @7103 및 @7104에 각각 a와 b라는 프로퍼티 이름을 저장
  • 데이터 영역에서 숫자 1을 검색. 검색 결과가 없으므로 임의로 @5003에 저장하고, 이 주소를 @7103에 저장. 문자열 'bbb'역시 임의로 @5004에 저장하고, 이 주소를 @7104에 저장

 

 

  • 객체의 변수(프로퍼티)의 영역이 별도로 존재한다는 점에서 기본형 데이터와 차이가 있음
  • 객체가 별도로 할애한 영역은 변수 영역일 뿐 '데이터 영역'은 기존의 메모리 공간을 그대로 활용하고 있음
  • 데이터 영역은 불변값이나 변수는 변경 가능해서 가변값이라고 함.

 

참조형 데이터의 재할당

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

 


변수 영역
주소 1001 1002 1003 1004 1005
데이터   이름: obj1
값: @5001
     

데이터 영역
주소 5001 5002 5003 5004 5005
데이터 @7103 ~ ?   1 'bbb' 2

객체 @5001의
변수 영역
주소 7103 7104 7105 7106
데이터 이름: a
값: @5005
이름: b
값: @5004
 

  • 5번째 줄 데이터 영역에 숫자 2가 없으므로 빈 공간인 @5005에 저장하고, 이 주소를 @7103에 저장
  • 변수 obj1이 바라보고 있는 주소는 @5001로 변하지 않았음
  • 새로운 객체가 만들어진 것이 아니라 기존의 객체 내부의 값만 바뀐 것

 

 

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

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

 


변수 영역
주소 1001 1002 1003 1004 1005
데이터   이름: obj1
값: @5001
 

데이터 영역
주소 5001 5002 5003 5004 5005
데이터 @7103 ~ ? 3 @8104 ~ ? 4 5

객체 @5001의
변수 영역
주소 7103 7104 7105 7106
데이터 이름: x
값: @5002
이름: arr
값: @5003
 


객체 @5003의
변수 영역
주소 8104 8105 8106  
데이터 이름: 0
값: @5002
이름: 1
값: @5004
이름: 2
값: @5005


  • 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj로 지정
  • 임의의 데이터 저장공간(@5001)에 데이터를 저장하려는데, 이 데이터는 여러 개의 변수와 값들을 모아놓은 그룹(객체)임. 이 그룹의 각 변수(프로퍼티)들을 저장하기 위해 별도의 변수 영역을 마련하고(@7103 ~ ?), 그 영역의 주소를 (@5001)에 저장함
  • @7103에 이름 x를, @7104에 이름 arr를 지정
  • 데이터 영역에서 숫자 3을 검색. 없으므로 @5002에 저장하고 이 주소를 @7103에 저장
  • @7104에 저장할 값은 배열로서 역시 데이터 그룹. 이 그룹 내부의 프로퍼티들을 저장하기 위해 별도의 변수 영역을 마련하고(@8104~?), 그 영역의 주소 정보를 @5003에 저장. @5003을 7104에 저장
  • 배열의 요소가 총 3개이므로 3개의 변수 공간을 확보하고 각각 인덱스 부여.
  • 데이터 영역에서 숫자 3을 검색해서(@5002) 그 주소를 @8104에 저장
  • 데이터 영역에 숫자 4가 없으므로 @5004에 저장하고, 이 주소를 @8105에 저장
  • 데이터 영역에 숫자 5가 없으므로 @5005에 저장하고, 이 주소를 @8106에 저장

 

  • obj.arr[1]을 검색하는 경우
    1. obj 검색 1: obj라는 식별자를 가진 주소를 찾음(@1001)
    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: 값이 주소이므로 그 주소로 이동
    9. obj.arr[1] 검색 3: 값이 숫자형 데이터이므로 4를 반환

 

 

obj.arr = 'str; // 재할당
  • 여기서 재할당 하게 되면 참조 카운트가 0인 메모리 주소는 가비지 컬렉터가 수거함
  • 참조 카운트: 어던 데이터에 대해 자신의 주소를 참조하는 변수의 개수

변수 영역
주소 1001 1002 1003 1004 1005 1006
데이터   이름: obj1
값: @5001
       

데이터 영역
주소 5001 5002 5003 5004 5005 5006
데이터 @7103 ~ ? 3 @8104 ~ ? 4 5 'str'

객체 @5001의
변수 영역
주소 7103 7104
데이터 이름: x
값: @5002
이름: arr
값: @5003

객체 @5003의
변수 영역
주소 8104 8105 8106
데이터 이름: 0
값: @5002
이름: 1
값: @5004
이름: 2
값: @5005

 

 

 

변수 복사 비교

 

변수 복사

var a = 10;
var b = a;

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

 


변수 영역
주소 1001 1002 1003 1004
데이터 이름: a
값: @5001
이름: b
값: @5001
이름: obj1
값: @5002 
이름: obj2
값: @5002

데이터 영역
주소 5001 5002 5003 5004
데이터 10 @7103 ~ ? 'ddd' 4

 


객체 @5002의
변수 영역
주소 7103 7104
데이터 이름: c
값: @5001
이름: d
값: @5003
  • 기본형 데이터에 대한 변수 선언 및 할당
    • 변수 영역의 빈 공간 @1001 확보하고 식별자를 a로 지정
    • 숫자 10을 데이터 영역에서 검색하고 없으므로 빈 공간 @5001에 저장한 다음, 이 주소를 @1001에 넣음. 
  • 변수 복사
    • 변수 영역의 빈 공간 @1002을 확보하고 식별자를 b로 지정
    • 식별자 a를 검색해 값을 찾기 위해 @1001에 저장된 값인 @5001을 들고 와 @1002에 값으로 대입

 

  • 참조형 데이터(객체)에 대한 변수 선언 및 할당
    • 변수 영역의 빈 공간 @1003을 확보해 식별자를 obj1로 지정
    • 데이터 영역의 빈 공간 @5002를 확보하고, 데이터 그룹이 담겨야 하기 때문에 별도의 변수 영역 @7103을 확보해 그 주소를 저장함
    • @7103에 식별자 c를, @7104에 식별자 d를 입력한 다음, c에 대입할 값 10을 데이터 영역에서 검색해 연결
    • 문자열 'ddd'는 데이터 영역의 빈 공간에 새로 만들어 @7104에 연결
  • 변수 복사
    • 변수 영역의 빈 공간 @1004를 확보하고 식별자를 obj2로 지정
    • 식별자 obj1을 검색해(@1003) 그 값인 @5002를 들고, @1004에 값으로 대입

 

변수를 복사하는 과정은 기본형 데이터와 참조형 데이터 모두 같은 주소를 바라보게 되는 점에서 동일함
(@1001과 @1002는 모두 값이 @5001이 됐고, @1003과 @1004에는 모두 값이 @5002가 됐음)

복사 과정은 동일하지만 데이터 할당 과정에서 이미 차이가 있기 때문에 변수 복사 이후의 동작에 큰 차이 있음

 

 

 

변수 복사 이후 값 변경 결과 비교(1) - 객체의 프로퍼티 변경 시

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

b = 15;
obj2.c = 20;

 


변수 영역
주소 1001 1002 1003 1004 1005
데이터 이름: a
값: @5001
이름: b
값: @5004
이름: obj1
값: @5002 
이름: obj2
값: @5002
 

데이터 영역
주소 5001 5002 5003 5004 5005
데이터 10 @7103 ~ ? 'ddd' 15 20

객체 @5002의
변수 영역
주소 7103 7104
데이터 이름: c
값: @5005
이름: d
값: @5003
  • 1~4번째 줄까지는 위의 코드와 같음
  • 6번째 줄에서 데이터 영역에 15가 없으므로 새로운 공간 @5004에 저장하고, 그 주소를 든 채로 변수 영역에서 식별자가 b인 주소를 찾음
  • @1002의 값이 @5004가 됨
  • 7번째 줄에서 데이터 영역에 20이 없으므로 새로운 공간 @5005에 저장하고, 그 주소를 든 채로 변수 영역에서 obj2를 찾고, obj2의 값인 @5002가 가리키는 변수 영역에서 다시 c를 찾아(@7103) 그곳에 @5005를 대입함

 

변수 a와 b는 서로 다른 주소를 바라보게 됐으나, 변수 obj1과 obj2는 여전히 같은 객체를 바라보고 있는 상태
a !== b
obj1 === obj2

 

왜 이런 차이가 발생하는가?

기본형이든 참조형이든 변수에 할당하기 위해서는 주솟값을 복사해야하기 때문에 자바스크립트의 모든 데이터 타입은 참조형 데이터임

다만 기본형은 주솟값을 복사하는 과정이 한 번만 이뤄지고, 참조형은 한 단계를 더 거치게 된다는 차이가 있는 것.

 

 

 

 

변수 복사 이후 값 변경 결과 비교(2) - 객체 자체를 변경했을 때

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

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

변수 영역
주소 1001 1002 1003 1004 1005 1006
데이터 이름: a
값: @5001
이름: b
값: @5004
이름: obj1
값: @5002 
이름: obj2
값: @5006
   

데이터 영역
주소 5001 5002 5003 5004 5005 5006
데이터 10 @7103 ~ ? 'ddd' 15 20 @8204 ~ ?

객체 @5002의
변수 영역
주소 7103 7104
데이터 이름: c
값: @5005
이름: d
값: @5003

객체 @5006의
변수 영역
주소 8204 8205
데이터 이름: c
값: @5005
이름: d
값: @5003
  • 프로퍼티를 변경했을 때(1)와 달리 객체 자체를 변경(2)한 경우에는 값이 달라짐(@5002 -> @5006)

 

즉, 참조형 데이터가 '가변값'이라고 설명할 때의 '가변'은 참조형 데이터 자체를 변경할 경우가 아니라 그 내부의 프로퍼티를 변경할 때만 성립
데이터 자체를 변경하고자 하면(새로운 데이터를 할당하고자 하면) 기본형 데이터와 마찬가지로 기존 데이터는 변하지 않음

 


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

 

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

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
  • changeName 함수가 새로운 객체를 반환하도록 수정

 

 

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

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
  • copyObject는 for in 문법을 이용해 result 객체에 target 객체의 프로퍼티들을 복사하는 함수
  • 이제 user는 불변 객체
  • 다만 copyObject함수를 사용할 때만 불변 객체이므로 immutable.js, baobab.js 등의 라이브러리를 사용하여 시스템적으로 제약을 거는 편이 더 안전함
  • 얕은 복사만을 수행한다는 점에서도 한계 있음

 

 

얕은 복사와 깊은 복사

  • 얕은 복사(shallow copy): 바로 아래 단계의 값만 복사하는 방법
    • ex) 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사 
    • 즉 원본과 사본이 동일한 참조형 데이터 주소를 가리키므로 사본을 바꾸면 원본도 변경됨
  • 깊은 복사(deep copy): 내부의 모든 값을 하나하나 찾아서 전부 복사하는 방법
    • ex) 중첩된 객체에서 프로퍼티 내부의 값들까지 전부 복사. 재귀로 구현 가능

 

깊은 복사

var copyObjectDeep = function(target) {
 	var result = {};
    if (typeof target === 'object' && target !== null) {
    for (var prop in target) {
    	result[prop] = copyObjectDeep(traget[prop]);
        }
   }else {
   	result = target;
  }
    return result;
}
  • target이 객체인 경우에는 내부 프로퍼티를 순회하며 copyObjectDeep 함수를 재귀 호출, 객체가 아닌 경우 target을 그대로 지정
  • 원본과 사본이 서로 완전히 다른 객체를 참조하게 하여 어느 쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않는다.

 


 

6. undefined와 null의 비교

 

  1. undefined
    • 값이 존재하지 않을 때 자바스크립트 엔진이 자동으로 부여
      • 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않는 식별자에 접근할 때
        • 빈 배열의 경우 undefined 아닌 [empty x 3(배열 크키)]로 나타남 -> 비어있어서 배열 메서드의 순회 대상에서 제외됨
      • 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때
      • return 문이 없거나 호출되지 않은 함수의 실행 결과
  2. null
    • '비어 있음'을 명시적으로 나타내고 싶을 때 사용
    • typeof null은 object라고 반환되는데, 이는 자바스크립트 자체 버그임. 따라서 변수의 값이 null인지 확인하려면 동등연산자(==) 대신 일치 연산자(===)로 확인해야함