
본 내용은 정재남 님의 코어 자바스크립트 책을 읽고 정리한 내용을 작성했습니다.
❓ 기본형과 참조형은 어떤 기준으로 참조 되는 것 일까?
- 기본형은 할당이나 연산시 복제되고 참조형은 참조되는 것으로 알려져 있고, 엄밀히 말하면 이 둘 모두 복제를 수행한다.
- 여기서 기본형은 값이 담긴 주소 값을 바로 복제하는 반면 참조형은 값이 담긴 주소 값이 이루어진 묶음을 가리키는 주소 값을 복제한다.
💡 기본형은 불변성을 가진다에 대하여
- Q. 기본형인 숫자 10을 담은 변수 a에 다시 숫자 15를 담으면 a의 값은 문제 없이 15로 변하는데, 이 불변하다는 것은 무슨 의미일까?
- A. 이것을 알려면 메모리와 데이터에 대한 지식이 필요로 하며, 메모리 영역에서 데이터가 어떻게 처리 되는지 이해할 필요가 있다.
비트 bit
바이트 byte
자바스크립트의 메모리
간혹 이 두 용어를 혼용해서 쓰는 경우가 많은데, 문맥에 따라 혼용해서 사용할 수 있지만 구분할 필요는 있다.
식별자
변수
var a;
변수 선언에 대한 메모리 영역의 변화 ( 변수 선언 과정 )
- 위 명령을 받은 컴퓨터는 메모리에서 비어있는 공간 하나를 확보한다.
- 표에 의하면 임의로 1003번으로 정하게 됐고, 이 공간의 이름(식별자)을 a라고 지정한다.
| 주소 | … | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: a | |||||
| 값: |
01 var a; // 변수 a 선언
02 a = 'abc'; // 변수 a에 데이터 할당
03
04 var a = 'abc'; // 변수 선언과 할당을 한 문장으로 표현
데이터 할당에 대한 메모리 영역의 변화 ( 데이터 할당 과정 )
- 변수 영역에서 빈 공간(@1003)을 확보한다.
- 확보한 공간의 식별자를 a로 지정한다.
- 데이터 영역의 빈 공간(@5004)에 문자열 ‘abc’를 저장한다.
- 변수 영역에서 a라는 식별자를 검색한다.(@1003)
- 앞서 저장한 문자열의 주소(@5004)를 @1003의 공간에 대입한다.
| 주소 | … | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: a | |||||
| 값: @5004 |
| 주소 | … | 5002 | 5003 | 5004 | 5005 | … |
|---|---|---|---|---|---|---|
| 데이터 | ‘abc’ |
❓ 왜 변수 영역에 값을 직접 대입하지 않고 한 단계를 더 거치는 걸까?
이유 1. 데이터 변환을 자유롭게 할 수 있다.
- 숫자형 데이터에 대해 64비트 공간을 확보하는 반면 문자열은 정해진 규격이 없어 한 글자마다 영어는 1바이트, 한글은 2바이트 등으로 각 필요한 메모리 용량이 가변적이고, 전체 글자 수 역시 가변적이다.
- 만약 미리 확보한 공간 안에 데이터 변환을 할 수 있다면 변환한 데이터를 다시 저장하기 위해서 확보된 공간을 변환된 데이터 크기에 맞게 늘리는 작업이 선행된다.
이유 2. 메모리를 더욱 효율적으로 관리할 수 있다.
- 중간에 데이터를 늘려야하는 상황이라면 해당 공간보다 더 뒤에 저장된 데이터를 전부 뒤로 옮기고,
이동시킨 주소를 각 식별자에 다시 연결하는 작업이 필요하고, 그것은 즉 컴퓨터가 처리해야할 연산이 많다는 의미다.- 따라서 효율적으로 문자열 데이터의 변환을 처리하려면 변수와 데이터를 별도의 공간에 나누어 저장하는 것이 최적이다.
- 예로 500개의 변수를 생성해 모든 변수에 숫자 5를 할당한다고 하면 숫자형은 8바이트가 필요하므로 500 x 8, 즉 4000바이트를 사용하지만, 5를 별도의 공간에 한 번만 저장하고 해당 주소만 입력받는다면 주소 공간의 크기가 2 바이트라고 할 때, 500 x 2 + 8로 1008 바이트만 이용하면 된다.
문자열 변환에 대한 메모리 영역의 변화
| 주소 | … | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: a | |||||
| 값: @5004 |
| 주소 | … | 5002 | 5003 | 5004 | 5005 | … |
|---|---|---|---|---|---|---|
| 데이터 | ‘abc’ | ‘abcdef’ |
상수
기본형 데이터 할당 예시
- 한 번 만들어진 값은 가비지 컬렉팅을 당하지 않는 한 영원히 변하지 않으며, 변경은 새로운 데이터의 공간을 만들어 저장하고 그것을 참조하는 것을 통해서만 이루어진다.
01 var a = 'abc';
02 a = a + 'def';
03
04 var b = 5;
05 var c = 5;
06 b = 7;
- 1~2번째 줄에서 변수 a에 ‘abc’할당
- ‘def’를 추가하면 기존의 ‘abc’와 합쳐 새로운 문자열인 ‘abcdef’를 만들고 그 주소를 변수 a에 저장한다.
(이때 abc와 abcdef는 완전히 별개의 데이터다.)- 4번째 줄에서는 변수 b의 숫자5를 할당한다. 그러면 컴퓨터는 데이터 영역에서 5를 찾고, 없으면 그때 데이터 공간을 하나 만들어 저장한다.
- 5번째 줄에서 4번 줄에서 다시 같은 수인 5를 할당하려고 한다면, 컴퓨너틑 데이터 영역에서 5를 찾고, 4번째 줄에서 이미 만들어 놓은 값이 있어서 그 주소를 재활용한다.
- 6번째 줄처럼 변수 b의 값을 7로 바꾸면, 기존에 저장된 5 자체를 7로 바꾸는게 아니라 기존에 저장한 7을 찾아서 있으면 재활용하고, 없으면 새로 만들어서 b에 저장한다.
- 결국 5와 7 모두 다른 값으로 변경할 수는 없다.
Object.defineProperty, Object.freeze 등)참조형 데이터의 할당 예시
- 기존 데이터와의 차이점은 객체의 변수(프로퍼티) 영역이 별도로 존재한다는 것이다.
- 변수 영역만 제공할 뿐 데이터 영역은 기존의 메모리 공간을 그대로 활용한다.
- 그러므로 참조형 타입에서 제공하는 이 변수 영역에는 다른 값을 얼마든지 대입하고, 변경할 수 있다.
01 var obj1 = {
02 a: 1,
03 b: 'bbb'
04 };
| 주소 | 1001 | 1002 | 1003 | 1004 | … |
|---|---|---|---|---|---|
| 데이터 | 이름: obj1 | ||||
| 값: @5001 |
| 주소 | 5001 | 5002 | 5003 | 5004 | … |
|---|---|---|---|---|---|
| 데이터 | @7103 ~ ? | 1 | ‘bbb’ |
| 주소 | 7103 | 7104 | 7105 | 7106 | … |
|---|---|---|---|---|---|
| 데이터 | 이름 : a | ||||
| 값: @5003 | 이름: b | ||||
| 값: @5004 |
- 컴퓨터는 우선 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj1으로 지정한다.
- 임의의 데이터 공간(@5001)에 데이터를 저장하려고 보니 여러 개의 프로퍼티로 이루어진 데이터의 그룹임을 확인하게 된다.
- 그룹 내부의 프로퍼티를 저장하기 위한 별도의 변수 영역을 마련하고, 그 영역의 주소(@7103 ~ ?)를 @5001에 저장한다.
- @7103 및 @7104에 각각 a와 b라는 프로퍼티 이름을 지정한다.
- 데이터 영역에서 숫자 1을 검색한다. 검색 결과가 없으므로 임의의 주소 @5003에 저장하고, 이 주소를 @7103에 저장한다. 문자열 ‘bbb’ 역시 임의로 @5004에 저장하고, 이 주소를 @7104에 저장한다.
참조형 데이터의 프로퍼티 재할당 예시
01 var obj1 = {
02 a: 1,
03 b: 'bbb'
04 };
05 obj1.a = 2;
- 5번째 줄에 obj1의 a프로퍼티에 숫자 2를 할당한다.
- 데이터 영역에서 숫자 2가 있는지 검색한다.
- 검색 결과가 없으면 빈 공간인 @5005에 저장하고, 이 주소를 @7103에 저장한다.
- 변수 obj1이 바라보고 있는 주소 @5001은 변하지 않고, 기존의 객체 내부의 값에서 변경사항이 발생한다.
| 주소 | 1001 | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: obj1 | |||||
| 값: @5001 |
| 주소 | 5001 | 5002 | 5003 | 5004 | 5005 | … |
|---|---|---|---|---|---|---|
| 데이터 | @7103 ~ ? | 1 | ‘bbb’ | 2 |
| 주소 | 7103 | 7104 | 7105 | 7106 | 7107 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름 : a | |||||
| 값: @5005 | 이름: b | |||||
| 값: @5004 |
중첩된 참조형 데이터의 프로퍼티 할당 예시
참조형 데이터의 프로퍼티에 다시 새로운 참조형 데이터를 할당하는 경우 중첩 객체라고 부른다.
01 var obj = {
02 x: 3,
03 arr: [ 3, 4, 5 ]
04 };
| 주소 | 1001 | 1002 | 1003 | 1004 | 1005 | … |
|---|---|---|---|---|---|---|
| 데이터 | 이름: obj1 | |||||
| 값: @5001 |
| 주소 | 5001 | 5002 | 5003 | 5004 | 5005 | … |
|---|---|---|---|---|---|---|
| 데이터 | @7103 ~ ? | 3 | @8104 ~ ? | 4 | 5 |
| 주소 | 7103 | 7104 | … |
|---|---|---|---|
| 데이터 | 이름 : x | ||
| 값: @5002 | 이름: arr | ||
| 값: @5003 |
| 주소 | 8104 | 8105 | 8106 | … |
|---|---|---|---|---|
| 데이터 | 이름 : 0 | |||
| 값: @5002 | 이름: 1 | |||
| 값: @5004 | 이름: 2 | |||
| 값: @5005 |
- 변수 영역의 빈 공간(@1002)을 확보하고, 그 주소의 이름을 obj로 지정한다.
- 임의의 데이터 저장공간(@5001)에 데이터를 저장한다.
- 이때 데이터는 여러 개의 변수와 값들을 모아놓은 그룹(객체)이므로 이 그룹의 각 변수들을 저장하기 위해 별도의 변수 영역을 생성하고(@7103~?), 그 영역의 주소를 @5001에 저장한다.
- @7103에 이름 x, @7104에 이름 arr를 지정한다.
- 데이터 영역에서 숫자 3을 검색한다.
- 숫자 3이 없기 때문에 임의의 @5002에 저장하고, 이 주소를 @7103에 저장한다.
- @7104에 저장할 값은 배열로서 역시 데이터 영역에서 검색한다.
- 해당하는 주소 값이 없기 때문에 이 그룹 내부의 프로퍼티를 저장하기 위해 별도의 변수 영역을 마련하고(@8104~?), 그 영역의 주소 정보(@8104~?)를 @5003에 저장한 후, @5003을 @7104에 저장한다.
- 배열의 요소가 총 3개이므로 3개의 변수 공간을 확보하고 각 인덱스를 부여한다.
- 데이터 영역에서 숫자 3을 검색해서(@5002) 그 주소를 @8104에 저장한다
- 데이터 영역에 숫자 4가 없으므로 @5004에 저장하고, 이 주소를 @8105에 저장한다.
- 데이터 영역에 숫자 5가 없으므로 @5005에 저장하고, 이 주소를 @8106에 저장한다.
여기서 obj.arr[1]을 검색하고자 하면 메모리에서는 다음과 같은 검색 과정을 거친다.
@1002 → @5001 → (@7103 - ?) → @7104 → @5003 → (@8104 - ?) → @8105 → @5004 → 4 반환
중첩된 참조형 데이터의 프로퍼티 재할당 예시
현재 상태에서 아래와 같은 재할당 명령을 내리면 어떻게 동작할까?
01 var obj = {
02 x: 3,
03 arr: [ 3, 4, 5 ]
04 };
05 obj.arr = 'str';
| 주소 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | … |
|---|---|---|---|---|---|---|---|
| 데이터 | 이름: obj1 | ||||||
| 값: @5001 |
| 주소 | 5001 | 5002 | 5003 | 5004 | 5005 | 5006 | … |
|---|---|---|---|---|---|---|---|
| 데이터 | @7103 ~ ? | 3 | @8104 ~ ? | 4 | 5 | ‘str’ |
| 주소 | 7103 | 7104 | … |
|---|---|---|---|
| 데이터 | 이름 : x | ||
| 값: @5002 | 이름: arr | ||
| 값: @5006 |
| 주소 | 8104 | 8105 | 8106 | … |
|---|---|---|---|---|
| 데이터 | 이름 : 0 | |||
| 값: @5002 | 이름: 1 | |||
| 값: @5004 | 이름: 2 | |||
| 값: @5005 |
- @5006에 문자열 ‘str’을 저장하고, 그 주소를 @7104에 저장한다.
- @5003은 더이상 자신의 주소를 참조하는 변수가 없는 상태가 된다.
- 참조하고 있는 카운트 수가 0이 되면서 해당 메모리 주소들은 가비지 컬렉터의 수거 대상이 된다.
- 가비지 컬렉터는 런타임 환경에 따라 특정 시점 혹은 메모리 사용량이 포화 상태에 임박할 때마다 자동적으로 수거 대상들을 수거한다.
- 수거된 메모리는 다시 새로운 값을 할당할 수 있는 빈 공간이 된다.
- @5003이 수거되고 나면 이후 이를 참조하고 있던 @8104 ~ ? 범위의 각 데이터들도 참조 카운트가 0이 되면서 GC 대상이 되어 이후에 사라지게 된다.
기본 타입과 참조 타입 - 변수 복사 비교 예시
01 var a = 10;
02 var b = a;
03
04 var obj1 = { c: 10, d: 'ddd' };
05 var obj2 = obj1;
| 주소 | 1001 | 1002 | 1003 | 1004 | … |
|---|---|---|---|---|---|
| 데이터 | 이름: a | ||||
| 값: @5001 | 이름: b | ||||
| 값: @5001 | 이름: obj1 | ||||
| 값 : @5002 | 이름: obj2 | ||||
| 값 : @5002 |
| 주소 | 5001 | 5002 | 5003 | 5004 | … |
|---|---|---|---|---|---|
| 데이터 | 10 | @7103 ~ ? | ‘ddd’ | … |
| 주소 | 7103 | 7104 | … |
|---|---|---|---|
| 데이터 | 이름 : c | ||
| 값: @5001 | 이름: d | ||
| 값: @5003 |
- 기본형의 경우 변수 a를 b에 복사한다면 다음과 같은 일이 벌어진다.
- @1001에 저장된 값인 @5001을 b에 확보해둔 @1002 공간에 값으로 할당한다.
- 참조형의 경우 변수의 영역을 저장하기 때문에 값을 복사하면 그 식별자 역시 같은 변수의 영역을 가리키는 주소 값을 저장하게 된다.
기본 타입과 참조 타입 - 변수 복사 이후 값 변경 결과 비교 예시(1)
01 var a = 10;
02 var b = a;
03
04 var obj1 = { c: 10, d: 'ddd' };
05 var obj2 = obj1;
06
07 b = 15;
08 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 |
| 주소 | 7103 | 7104 | … |
|---|---|---|---|
| 데이터 | 이름 : c | ||
| 값: @5005 | 이름: d | ||
| 값: @5003 |
💡 기본형 복사와 참조형 복사에 대해
- 사실 자바스크립트에서 모든 데이터 타입은 참조형 데이터다.
다만 기본형은 주소 값을 복사하는 과정이 한 번만 이루어지고, 참조형은 한 단계를 더 거치게 된다는 차이가 있다.
기본 타입과 참조 타입 - 변수 복사 이후 값 변경 결과 비교 예시(2)
만약 obj2에 새로운 객체를 할당함으로써 값을 직접 변경하면 어떻게 될까?
01 var a = 10;
02 var b = a;
03
04 var obj1 = { c: 10, d: 'ddd' };
05 var obj2 = obj1;
06
07 b = 15;
08 obj2.c = { 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 ~ ? |
| 주소 | 7103 | 7104 | … |
|---|---|---|---|
| 데이터 | 이름 : c | ||
| 값: @5001 | 이름: d | ||
| 값: @5003 |
| 주소 | 8204 | 8205 | … |
|---|---|---|---|
| 데이터 | 이름 : c | ||
| 값: @5005 | 이름: d | ||
| 값: @5003 |
불변 객체를 만드는 간단한 방법
새로운 객체를 만드는 라이브러리 등을 사용
ES6의 메서드 활용하기
불변 객체 문제 해결 하는 방법 - 함수를 통해 새로운 객체를 생성
var user = {
name: 'Ethan',
gender: 'male'
};
var changeName = function(user, newName) {
return {
name: newName,
gender: user.gender
};
};
ver user2 = changeName(user, 'Winters');
if(user !== user2) {
console.log('유저의 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // Ethan Winters
console.log(user === user2); // false
불변 객체 문제 해결 하는 방법 - 기존 정보를 복사해서 새로운 객체를 생성(얕은 복사)
var copyObject = function(target) {
var result = {};
for (var prop in target) {
result[prop] = target[prop];
}
return result;
};
var user = {
name: 'Ethan',
gender: 'male'
};
var user2 = copyObject(user);
user2.name = 'Winters';
if(user !== user2) {
console.log('유저의 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // Ethan Winters
console.log(user === user2); // false
얕은 복사 Shallow Copy
깊은 복사 Deep Copy
내부의 모든 프로퍼티를 직접 전부 복사하는 방법
프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게 하는 방법
hasOwnProperty 메서드 활용하기ES5의 getter/setter를 복사하는 방법
Object.getOwnPropertyDescriptorObject.getOwnPropertyDescriptors객체의 깊은 복사를 수행하는 함수로 개선 ( 재귀 함수 )
var copyObjectDeep = function(target) {
var result = {};
if(typrof target === 'object' && target !== null) {
for (var prop in target) {
result[prop] = copyObjectDeep(target[prop]);
}
} else {
result = target;
}
return result;
};
copyObjectDeep 함수를 재귀적으로 호출한다.깊은 복사 결과 확인 예시
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, 2] } }
console.log(obj2); // { a: 3, b: { c: 4, d: [1, 3] } }
JSON을 활용한 간단한 깊은 복사 예시
- JSON 문법의 문자열로 변환했다가 다시 JSON 객체로 변환하여 깊은 복사를 수행할 수 있다.
var copyObjectViaJSON = function(target) {
return JSON.parse(JSON.stringify(target));
};
자바스크립트에서 ‘없음’을 나타내는 값으로, 이 두 값은 의미도 살짝 다르고 사용하는 목적도 다르다.
자동으로 undefined를 부여하는 경우 예시
다음과 같은 경우에 그 위치에 undefined를 반환한다.
01 var a;
02 console.log(a); // (1) undefined. 값을 대입하지 않은 변수에 접근
03
04 var obj = { a: 1 };
05 console.log(obj.a); // 1
06 console.log(obj.b); // (2) undefined. 존재하지 않은 프로퍼티에 접근
07 console.log(b); // c.f) ReferenceError: b is not defined
08
09 var func = function() {};
10 var c = func(); // (3) 반환 값이 없으면 암시적으로 undefined를 반환한 것으로 간주한다.
11 console.log(c); // undefined
undefined와 배열 예시
01 var arr1 = [];
02 arr1.length = 3;
03 console.log(arr1); // [empty x 3]
04
05 var arr2 = new Array(3);
06 console.log(arr2); // [empty x 3]
07
08 var arr3 = [undefined, undefined, undefined];
09 console.log(arr3); // [undefined, undefined, undefined]
빈 요소와 배열의 순회 예시
비어있는 요소(empty)와 undefined를 할당한 요소는 출력한 결과가 다르다.
01 var arr1 = [undefined, 1]; // [undefined, 1]
02 var arr2 = [];
03 arr2[1] = 1; // [empty, 1]
04
05 arr1.forEach(function(v, i) { console.log(v, i); }); // undefined 0 / 1 1
06 arr2.forEach(function(v, i) { console.log(v, i); }); // 1 1
07
08 arr1.map(function(v, i) { return v + i; }); // [NaN, 2]
09 arr2.map(function(v, i) { return v + i; }); // [empty, 2]
10
11 arr1.filter(function(v) { return !v; }); // [undefined]
12 arr2.filter(function(v) { return !v; }); // []
13
14 arr1.reduce(function(p, c, i) { return p + c + i; }, ''); // undefined011
15 arr2.reduce(function(p, c, i) { return p + c + i; }, ''); // 11
명시적 undefined와 암묵적 undefined
명시적 undefined
암묵적 undefined
null 타입의 버그
typeof null === object01 var n = null;
02 console.log(typeof n); // object
03
04 console.log(n == undefined); // true
05 console.log(n == null); // true
06
07 console.log(n === undefined); // false
08 console.log(n === null); // true
메모리의 빈 공간에 식별자 저장
그 공간의 값에 undefined를 할당
이후 변수에 데이터를 할당
3-1. 기본형 데이터
3-2. 참조형 데이터 (여러 개의 변수들을 그룹으로 묶어 놓은 집합체)