비트(bit): 0, 1로 표현한 하나의 메모리 조각. 고유식별자로 위치 확인.
바이트(byte): 8개의 비트를 묶은 메모리 조각 .
*바이트의 필요성: 비트 대비 값의 표현 개수 증가, 검색 시간 감소, 낭비되는 비트 최소화
정적타입 언어(C/C++, java)는 메모리 낭비 최소화 방안으로 데이터 타입별 메모리 영역을 2byte, 4byte 등으로 정해놓음
Javascript는 메모리용량이 커진 후 나온 언어라서 메모리 압박으로부터 자유로움
모든 데이터는 바이트 단위의 식별자인 메모리 주솟값을 통해 서로 구분하고 연결함.
변수(variable): 변할 수 있는 수, 데이터
식별자(identifier): 변수명
var a; // a는 식별자이며 변할 수 있는 데이터를 담는 공간, 그릇
변수영역
주소 | ... | 1002 | 1003 | 1004 |
---|---|---|---|---|
데이터 | 이름: a | |||
값: |
var a; //변수 a의 선언
a='abc'; //변수 a에 데이터 할당
var a = 'abc' //변수 선언과 할당을 한 문장에 표현
왜 변수 영역에 값을 직접 대입하지 않을까?
- 메모리의 효율적 관리를 위해서!
- 문자열은 필요한 메모리용량과 글자수가 가변적이다
- 직접 대입한다면? 변환된 데이터가 크다면 미리 확보된 공간에 대입하기 위해 공간 확장 작업이 필요함! 해당 공간 뒤에 저장된 데이터를 옮겨야 하고 이동시킨 주소를 식별자에 다시 연결해야 하고.. 연산량 증가!비효율!
- 변수영역/데이터영역 나누면 중복된 데이터에 대한 처리효율 높아짐!
변수(variable) | 변수영역 변경 가능(=재할당 가능) |
상수(constant) | 변수영역 변경 불가(=재할당 불가) |
불변값 | 데이터영역 변경 불가 |
가변값 | 데이터영역 변경 가능 |
var a = 'abc';
a = a + 'def'; // @5003의 'abc'를 'abcdef'로 바꾸는 게 아니라
// 데이터 영역에 'abcdef'를 새로 만들고 주소 저장
var b = 5;
var c = 5; // 데이터 영역에서 5를 검색, 있으면 주소 재활용
b=7; // @5005의 5를 7로 바꾸는게 아니라
// 데이터 영역에서 7을 검색, 없으면 새로 만들고 주소 저장
변수영역
주소 | ... | 1002 | 1003 | 1004 | 1005 |
---|---|---|---|---|---|
데이터 | 이름: a | 이름: b | 이름: c | ||
값: | 값: | 값: @5005 |
데이터영역
주소 | ... | 5002 | 5003 | 5004 | 5005 | 5006 | 5007 | 5008 |
---|---|---|---|---|---|---|---|---|
데이터 | 'abc' | 'abcdef' | 5 | 7 |
참조형 데이터의 할당과 프로퍼티 재할당:
var obj1 = {
a: 1,
b: 'bbb'
};
obj1.a = 2; //변수영역(@1002)은 변하지 않음. 객체 내부의 값만 바뀜.
변수영역
주소 | 1001 | 1002 | 1003 | 1004 | 1005 |
---|---|---|---|---|---|
데이터 | 이름: obj1 | ||||
값: @5001 |
데이터영역
주소 | 5001 | 5002 | 5003 | 5004 | 5005 | 5006 | 5007 |
---|---|---|---|---|---|---|---|
데이터 | 이름: a | 1 | 'bbb' | 2 | |||
값: @7003~? |
객체 @5001의 변수영역
주소 | 7003 | 7004 | 7005 |
---|---|---|---|
데이터 | 이름: a | 이름: b | |
값: | 값: @5004 |
- 중첩객체(nested object): 참조형 데이터의 프로퍼티에 다시 참조형 데이터를 할당
- 참조 카운트: 어떤 데이터에 대해 자신의 주소를 참조하는 변수의 개수
- 가비지 컬렉터(garbage collector, GC): (런타임 환경에 따라 특정 시점이나 메모리 사용량 포화 임박했을 때마다) 참조 카운트가 0인 메모리주소를 수거하여 새로운 값이 할당되도록 빈 공간으로 만드는 것.
var obj = {
a: 3,
arr: [3, 4, 5] // 중첩 객체
};
obj.arr = 'str';
👉 GC 대상: 데이터영역(@5003), 객체 @5003의 변수영역(@8001~@8003)
변수영역
주소 | 1001 | 1002 | 1003 | 1004 | 1005 |
---|---|---|---|---|---|
데이터 | 이름: obj | ||||
값: @5001 |
데이터영역
주소 | 5001 | 5002 | 5003 | 5004 | 5005 | 5006 | 5007 |
---|---|---|---|---|---|---|---|
데이터 | 이름: a | 3 | 이름: arr | 4 | 5 | 'str' | |
값: @7003~? | 값: @8001~? |
객체 @5001의 변수영역
주소 | 7003 | 7004 | 7005 |
---|---|---|---|
데이터 | 이름: a | 이름: arr | |
값: @5002 | 값: |
객체 @5003의 변수영역
주소 | 8001 | 8002 | 8003 |
---|---|---|---|
데이터 | 이름: 0 | 이름: 1 | 이름: 2 |
값: @5002 | 값: @5004 | 값: @5005 |
- 변수를 복사했을 때 기본형과 참조형 데이터 모두 같은 주소(@5001, @5002)를 바라보게 되는 점에서 동일하다
- 복사한 변수의 값을 변경했을 때 기본형 a,b는 서로 다른 주소(@5001, @5004)를 바라보게 됐고 참조형 obj1, obj2는 서로 같은 주소(@5002)를 바라본다는 점이 다르다.
a !== b obj1 === obj2
- '기본형은 값을 복사하고 참조형은 주솟값을 복사한다'(△)
👉 엄밀히 따지면 자바스크립트의 모든 데이터 타입은 참조형 데이터이다.
👉 기본형은 주솟값 복사 과정이 한 번만 있고 참조형은 한 단계 더 거친다.- 참조형데이터가 가변값인 경우는 내부의 프로퍼티를 변경할 때만 성립한다
객체 프로퍼티를 변경 시
var a = 10;
var b = a; // 기본형 복사
var obj1 = {
c: 10,
d: 'ddd'
};
var obj2 = obj1; // 참조형 복사
b = 15; //기본형) 복사한 변수의 값을 변경
obj2.c = 20; //참조형) 복사한 변수의 값을 변경
변수영역
주소 | 1001 | 1002 | 1003 | 1004 | 1005 |
---|---|---|---|---|---|
데이터 | 이름: a | 이름: b | 이름: obj1 | 이름: obj2 | |
값: @5001 | 값: | 값: @5002 | 값: @5002 |
데이터영역
주소 | 5001 | 5002 | 5003 | 5004 | 5005 |
---|---|---|---|---|---|
데이터 | 10 | @7103~? | 'ddd' | 15 | 20 |
객체 @5002의 변수영역
주소 | 7103 | 7104 |
---|---|---|
데이터 | 이름: c | 이름: d |
값: | 값: @5003 |
참조형 데이터 자체를 변경한 경우에는 불변값이다
객체 자체를 변경 시
var a = 10;
var b = a; // 기본형 복사
var obj1 = {
c: 10,
d: 'ddd'
};
var obj2 = obj1; // 참조형 복사
b = 15; //기본형) 복사한 변수의 값을 변경
obj2 = {
c: 20,
d: 'ddd'
}; //참조형) 복사한 변수의 값을 변경
변수영역
주소 | 1001 | 1002 | 1003 | 1004 | 1005 |
---|---|---|---|---|---|
데이터 | 이름: a | 이름: b | 이름: obj1 | 이름: obj2 | |
값: @5001 | 값: | 값: @5002 | 값: |
데이터영역
주소 | 5001 | 5002 | 5003 | 5004 | 5005 | 5006 |
---|---|---|---|---|---|---|
데이터 | 10 | @7103~? | 'ddd' | 15 | 20 | @8204~? |
객체 @5002의 변수영역
주소 | 7103 | 7104 |
---|---|---|
데이터 | 이름: c | 이름: d |
값: @5001 | 값: @5003 |
객체 @5006의 변수영역
주소 | 8204 | 8205 |
---|---|---|
데이터 | 이름: c | 이름: d |
값: @5005 | 값: @5003 |
객체의 불변성을 확보하려면 내부 프로퍼티를 변경할 필요가 있을 때마다 '매번 새로운 객체를 만들어 재할당'하거나, '자동으로 새로운 객체를 만드는 도구'를 활용하면 된다.
어떤 상황에서 불변 객체가 필요할까?
값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우
1) 기존 정보를 복사해 새로운 객체를 반환; 얕은 복사(shallow copy)
var copyObject = function(target){
var result = {};
for(var property in target){
result[property] = target[property];
}
return result;
};
2) 깊은 복사(deep copy)
'target !== null' 조건을 덧붙인 이유는 자바스크립트 자체 버그로 typeof 명령어가 null에 대해서 'object'를 반환하기 때문이다
var copyObjectDeep = function(target){
var result = {};
if(typeof target === 'object' && target !== null){
// target이 객체인 경우
for (var property in target){ // 내부 프로퍼티를 순회하며
result[property] = copyObjectDeep(target[property]);
// 함수를 재귀호출
}
}else{
result = target; // 객체가 아니면 그대로 지정
}
return result;
}
그외 깊은 복사 방법
- hasOwnProperty 메서드: 프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게 함.
- 객체를 JSON 문법으로 표현된 문자열로 전환 후 다시 JSON객체로 바꾸기.
3) 프로퍼티 변경을 할 수 없게 시스템적 제약을 거는 라이브러리: immutable.js, baobab.js 등
undefined: 사용자가 명시적 지정(굳이?), 값이 존재하지 않을 때 JS엔진이 자동 부여
null: 사용자가 '비어있음'을 명시적 지정
배열도 객체이므로 값이 지정되지 않은 인덱스는 아직 존재하지 않는 프로퍼티 이다. 특정 인덱스에 값을 지정할 때 비로소 빈 공간을 확보하고 인덱스를 이름으로 지정하고 데이터의 주솟값을 저장하는 동작을 한다
var arr1 = [undefined, 1]; var arr2 = []; arr2[1] = 1; // 인덱스(i), 값(v), (p) arr1.forEach(function(v,i){console.log(v,i);}); //undefined 0: 0번 인덱스의 값은 undefined입니다 //1 1: 1번 인덱스의 값은 1입니다 arr2.forEach(function(v,i){console.log(v,i);}); //1 1: (0번은 순회하지 않음) 1번 인덱스의 값은 1입니다 arr1.map(function(v,i){return v+i);}); //[NaN, 2]: 0 + undefined = NaN, 1 + 1 = 2 arr2.map(function(v,i){return v+i);}); //[empty, 2] (비어있어서)empty, 1 + 1 = 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': 이전반환값+현재처리할값+인덱스 => 'undefined'가 string이므로 이후의 덧셈 결과가 string으로 됨 arr2.reduce(function(p,c,i){return p+c+i);}); //'11': (0번 인덱스는 비어있어서 순회하지 않음) 왜 2가 아닌 '11'인가?
[코어 자바스크립트], 정재남 저, 위키북스