ES6 이전까지의 자바스크립트의 전반적인 흐름을 공부합니다.
자바스크립트의 두 개의 데이터 타입의 차이점과 그 차이가 발생하는 원인에 대해서 살펴 볼 것이다.
Primitive Type 기본형
- Number
- String
- Boolean
- null
- undefined
- Symbol (ES6)
Reference Type 참조형
Object
- Array
- Function
- RegExp (정규 표현식)
위의 두 가지 데이터 타입을 구별해야되는 이유를 메모리를 살펴보면서 공부해 볼 것이다.
기본형 타입의 메모리 흐름
let a;
a라는 변수를 선언하면 메모리 안에 데이터가 담길 공간을 미리 확보한다. 1003번에 이름, 값의 위치를 잡아놓는다.
확보한 공간의 이름을 식별자 'a'로 지정을 한다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | | 이름: a | | | |
a = 'abc';
이후 'abc'라는 문자열을 할당하라는 명령어를 넣으면 우선 문자열 'abc'를 메모리의 다른 빈 공간에 넣는다.
그리고 그 메모리에 있는 주솟값 5004번을 들고, 식별자 'a'가 있는 1003번으로 가서 1003의 값에 @5004을 넣는다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | | 이름: a 값: @5004 | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | | | 'abc' | | |
a = 'qwert';
이후 다시 'qwert'라는 문자열을 재할당면 또 임의의 공간인 5005에다 미리 확보를 한 뒤 저장을 하고, 식별자 'a'가 위치한 1003번으로 가서 값을 @5004번 대신 @5005으로 덮어 씌우기를 한다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | | 이름: a 값: @5005 | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | | | 'abc' | 'qwert' | |
참조형 타입의 메모리 흐름
let obj = {
a: 1,
b: 'bbb',
};
우선 obj를 선언하는 과정이 먼저니까 1002번에 공간을 확보해서 식별자 이름을 obj로 넣는다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj | | | | |
그다음에는 할당을 해야되는데 할당을 하려고 보니까 (중간에 다른 데이터들 처리..) 5002번에 공간을 확보해놨는데 5002번 안에다 데이터를 통으로 넣을 수 있을까?
생각해봤는데 메모리 구조 공간상 들어갈 수 있는 데이터 구조가 1개씩밖에 안 들어간다.
어 그러면 여러개를 넣어놓고, 그 여러개를 묶음 처리해서 그 주소를 넣어야겠다!
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | | | | |
7103번 이후부터 어디까지인지는 모르겠지만 공간을 많이 확보해놓고, 거기에 들어가있는 데이터들은 전부 이 객체야 —> 라고 만들어 놓고 7103 ~ 부터 데이터들을 하나 씩 넣을것이다.
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | | | | | |
a: 1,
a라는 property에 1을 넣으라고 했으니까 a를 넣는 공간을 확보하고 1을 넣는 과정을 다른 빈 데이터에 넣어서 주소를 값에 넣는다.
b: 'bbb'
b도 a와 마찬가지로 넣는 공간을 확보하고 'bbb'를 넣는 과정을 다른 빈 데이터에 넣어서 주소를 값에 넣는다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | 1 | 'bbb' | | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: a 값: @5003 | 이름: b 값: @5004 | | | |
그렇게해서 객체 안에 있는 내용들에 할당이 다 끝나면 그제서야 7103부터 이후에 있는 개체들이 담겨 있는 주소를 가리키고 있는 5002번에 있는 주소를 obj에 넣게 된다.
📣 참조형 타입은 한 단계를 더 거쳐가는 형태가 된다.
obj.a = 2;
이번에는 a라는 property의 값을 2로 바꾸는것을 생각해 볼 것이다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | 1 | 'bbb' | | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: a 값: @5003 | 이름: b 값: @5004 | | | |
- 일단 obj를 찾으면 obj를 가리키고 있는 1002번을 찾아간다.
- 1002번이 가리키고 있는 5002번으로 간다.
- 5002번을 가보니까 @7103 ~ 를 가리키고 있어서 다시 가리키는 곳으로 간다.
- 먼저 a property를 찾는다!
- a property를 가리키고 있는 것을 찾으면 7103이다.
- 7103에 있는 값을 2로 대체해라 했을 때 2를 할당해야 되기 때문에 새로 2를 만들어서 그 주소를 7103에 주소를 새로 바꿔치기 해줘야 된다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | 1 | 'bbb' | 2 | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: a 값: @5005 | 이름: b 값: @5004 | | | |
그러면 여기까지 obj.a에 있는 값은 5005로 변경이 됐지만, obj 이름이 가리키고 있는 주소는 변경되지 않았다.
—> 객체는 obj.a 프로퍼티의 값을 변경해도 변하지 않는다. 이게 기본형과 참조형의 차이점이다.
중첩 객체
let obj = {
x: 3,
arr: [3, 4],
}
- 일단 obj를 1002번에 확보하고 그 확보된 공간에 객체를 담아야 되니까 5002번에 있는 주소를 연결하고,
- 그 5002번에 있는 주소는 여러개의 자리를 미리 만들고 7103 ~ 를 가리키고 있다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | | | | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | | | | | |
3. x property를 할당해야되니까 7103에다가 이름에는 x를 넣고, 값에다가는 5003번에 있는 3의 값의 주소를 넣는다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | 3 | | | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: x 값: @5003 | | | | |
4. 다음 arr 이라는 이름을 가진 식별자를 7104에 부여했는데 여기에 있는 값을 넣으려고 보니까 또 다시 참조형 데이터이다.
그래서 또 많은 공간을 확보해둬야 돼서 5004번에 다가 8104 ~ 부터 공간을 확보해두었다.
그리고 5004번을 7104에다가 넣는다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | 3 | @8104 ~ ? | | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: x 값: @5003 | 이름: arr 값: @5004 | | | |
주소 | ... | 8104 | 8105 | 8106 | 8107 | ... |
---|
데이터 | | | | | | |
- 이제 arr 배열에 있는 값 3, 4를 넣기 위해서 메모리를 보면 이미 선언된 3이라는 값이 있어서 그걸 그대로 가져다 사용하면 된다.
4는 없으니까 새로 만들어서 넣는다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|
데이터 | | @7103 ~ ? | 3 | @8104 ~ ? | 4 | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: x 값: @5003 | 이름: arr 값: @5004 | | | |
주소 | ... | 8104 | 8105 | 8106 | 8107 | ... |
---|
데이터 | | 이름: 0 값: @5003 | 이름: 1 값: @5005 | | | |
위의 상태에서 다시 obj.arr에 문자열 'hello'를 넣을 때를 살펴 볼 것이다.
obj.arr = 'hello';
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: obj 값: @5002 | | | | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | 5006 | ... |
---|
데이터 | | @7103 ~ ? | 3 | @8104 ~ ? | 4 | 'hello' | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: x 값: @5003 | 이름: arr 값: @5006 | | | |
주소 | ... | 8104 | 8105 | 8106 | 8107 | ... |
---|
데이터 | | 이름: 0 값: @5003 | 이름: 1 값: @5005 | | | |
- 먼저 obj가 있는 1002를 찾아가서 5002를 찾아가고, 7103부터 찾으면서 arr을 찾는다.
- arr이 7104에 있고, 다시 @5004대신에 새로운 문자열을 만들어서 넣으랬으니까 5006번에 문자열을 만들고 5006을 새로운 값으로 할당한다.
- 그러면 이 부분의 값을 들고있는 데이터가 없어지게 된다.
- 5004번이 없어짐으로써 참조하고있는 값이 사라지게되니까 5004번이 갖고있던 값들도 다 사라진다.
—> Garbage Collecting을 당하게 된다. —> 참조 카운트(참조를 하는 대상이 몇 개가 있는지?) 가 0이 됐다.
주소 | ... | 5002 | 5003 | 5004 | 5005 | 5006 | ... |
---|
데이터 | | @7103 ~ ? | 3 | @8104 ~ ? | 4 | 'hello' | |
주소 | ... | 8104 | 8105 | 8106 | 8107 | ... |
---|
데이터 | | 이름: 0 값: @5003 | 이름: 1 값: @5005 | | | |
변수 복사 살펴보기
기본형 데이터는 복사를 해도 가리키고 있는 주소를 그대로 가져오니까 같은 주소를 보게된다.
let a = 10;
let b = a;
let obj1 = { c: 10, d: 'ddd' };
let obj2 = obj1;
b = 15;
obj2.c = 20;
참조형 데이터의 내부에 있는 값을 바꿔도 둘이 같은 객체를 여전히 바라보고 있다.
그렇기 때문에 객체를 복사를 했음에도 불구하고 복사를 한 데이터의 값을 바꿨는데 원본데이터가 같이 바뀌는 이유를 알 수 있어야 된다.
var obj1 = {
c: 10,
d: 'ddd',
};
var obj2 = obj1;
해결 방안으로 매번 새로운 객체를 만들고, 불변성, 불변 객체를 만들어야 된다.
주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|
데이터 | | 이름: a 값: @5002 | 이름: b 값: @5005 | 이름: obj1 값: @5003 | 이름: obj2 값: @5003 | |
주소 | ... | 5002 | 5003 | 5004 | 5005 | 5006 | ... |
---|
데이터 | | 10 | @7103 ~ ? | 'ddd' | 15 | 20 | |
주소 | ... | 7103 | 7104 | 7105 | 7106 | ... |
---|
데이터 | | 이름: c 값: @5006 | 이름: d 값: @5004 | | | |
기억해둘 것
기본형 데이터들은 한 번 만들어진 값이 똑같은 게 있으면 재사용을 한다.
숫자의 값이 엄청나게 커졌을 경우, 똑같은 메모리에서 1개만 쓰는 것.
—> 똑같은 값을 사용하면 1개의 메모리만 사용한다.
JavaScript의 메모리 관리
C언어같은 저수준 언어에서는 메모리 관리를 위해 malloc, free를 사용한다고 한다.
반면에 자바스크립트는 객체가 생성되었을 때 자동으로 메모리를 할당하고 쓸모 없어졌을 때 자동으로 해제한다. 이것을 가비지 컬렉션이라고 한다.
이러한 자동 메모리 관리는 잠재적 혼란의 원인이기도 한데, 개발자가 메모리 관리에 대해 고민할 필요가 없다는 잘못된 인상을 줄 수 있다고 한다.
메모리 생존주기
- 필요할 때 할당한다.
- 사용한다. (읽기, 쓰기)
- 필요없어지면 해제한다.