Map, Set :
Map은 object ({ }) 와 유사하고 Set은 array( []) 와 유사하다.
처음에는 배우기 쉽도록 기본형은 데이터를 그대로 할당, 참조형은 그 데이터가 들어가 있는 주소를 할당한다고 배웠으나,
실질적으로는 둘 다 주소를 할당하되
기본형은 값이 담긴 주솟값을 바로 할당하고,
참조형은 값이 담긴 주솟값으로 이루어진 묶음을 가리키는 주솟값을 할당한다고 설명한다.
우리가 입력하는 모든 데이터들은 2진수로 변환되어 하나의 숫자(Bit)가 고유한 식별자를 가지게 되는데
이 고유한 식별자 덕분에 비트의 주소를 알 수 있다.
이를 메모리 주솟값(memory address)이라 한다.
let mcCommi = 25;
mcCommi : 식별자
25 : 변수( 변경 가능한 데이터가 담기는 공간)
변수영역 주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|---|---|---|---|---|---|
Data | ... | 이름: mcCommi값: @5003 | ... |
데이터 영역 주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|---|---|---|---|---|---|
Data | ... | 25 | ... |
이를 토대로 다시 생각한다면
기본형 데이터 타입 역시 값을 직접 할당하는게 아닌,
값이 담긴 주솟값을 할당한다는것을 알 수 있다.
이를 통해 가져갈 수 있는 이점이 있는데
데이터 변환이 자유로워진다.
고유한 식별자를 가지고 있는 비트들을 쉬프트 이동시키고 다시 할당하고 할 필요 없이, 새로운 데이터를 추가하고 해당되는 주소값만 연결시키면 된다.
메모리자원을 더욱 효율적으로 관리 할 수 있다.
변수영역과 데이터 영역이 분리되어, 작은 크기의 주소공간만 저장되기때문에 메모리 자원을 더욱 효율적으로 관리할수잇다.
var + let / const 의 차이점은
한번 데이터 할당이 이뤄진 변수 영역에 다른 데이터를 재할당할 수 있는지 여부를 말한다.
let a = 1;
let b = a;
a = a + 1;
console.log(a) // 2
console.log(b) // 1
a의 값은 변햇으나 b의 값은 여전히 이전 a의 값을 가르키고 있다.
- 1이라는 값에 해당되는 주소를 a와 b에 할당한다.
- a의 값을 a+1로 변경한다.
- 2라는 값을 찾는다... 2가 없기 때문에 새롭게 공간은 찾고 2라는 값을 넣은 후 그 주소를 할당한다.
- 여전히 b는 이전 1의 주소를 가지고 있다.
즉 다시 본다면
1이 있었기 때문에 재활용 한것이고, 2가 없기 때문에 새로 생성한것이다.
값이 변경된 것이 아니다. 새롭게 값이 생성되어 할당된것이다.
한번 만든 값은 가비지 컬렉팅을 하지 않는한 지속적으로 남아있다.
이것이 바로 불변값이며, 기본형 데이터타입은 모두 불변값이다.
let obj = {
name: "McCommi",
age: 25
};
변수영역 주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|---|---|---|---|---|---|
Data | ... | 이름: obj값: @5003 | ... |
데이터 영역 주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|---|---|---|---|---|---|
Data | ... | @7002 ~... | 'mcCommi' | 25 | ... |
객체 영역 주소 | ... | 7002 | 7003 | 7004 | 7005 | ... |
---|---|---|---|---|---|---|
Data | ... | 이름:namevalue: @5004 | 이름:agevalue:@5005 | ... |
변수 영역에 빈공간을 찾아낸다: @1003, 식별자의 이름은 obj로 명명한다.
데이터 영역에 빈공간 @5003 을 찾아내고, 데이터를 저장한다.
이 때, 저장할 데이터는 여러 개의 프로퍼티로 이루어진 데이터 그룹
이기 때문에 이를 저장하기 위해 별도의 변수 영역을 만든다.
(서술상 임의로 객체 @1002 의 변수 영역이라고 부른다.)
객체 @5003 의 변수 영역의 주소인, @7002 ~ ... 을 @5003 에 저장한다.
객체 @5003 의 변수 영역에 각각 name (@7002) , age (@7003) 라는 프로퍼티 이름을 지정한다.
데이터 영역에 'mcCommi' 가 존재하지 않기 때문에,
새로 생성 한 후@5004 에 저장하고, 이 주소를 @7002 에 저장한다.
또한 , 25 라는 값이 존재하지 않기 때문에
데이터 영역에 새로 생성후 @5005 에 저장하고, 이 주소를 @7003 에 저장한다.
여기에서 만약 새로 나이를 26이라고 할당한다면
기존에 @7003 은 @5005 를 가르키지 않고 새로 생성된 26이라는 값이 존재하는 @5006 을 가르키게 될것이다.
허나, obj가 바라보는 주소인 @5003은 변하지 않는다. =
새로운 객체가 만들어진것이 아니다.
기존 객체의 내부에서 바라보는 값이 변한것이다.
let obj = {
name: "McCommi",
age: 25
};
let obj2 = obj
obj2.age = 27
변수영역 주소 | ... | 1002 | 1003 | 1004 | 1005 | ... |
---|---|---|---|---|---|---|
Data | ... | 이름: obj값: @5003 | 이름: obj2값: @5003 | ... |
데이터 영역 주소 | ... | 5002 | 5003 | 5004 | 5005 | ... |
---|---|---|---|---|---|---|
Data | ... | @7002 ~... | 'mcCommi' | 25 | ... |
객체 영역 주소 | ... | 7002 | 7003 | 7004 | 7005 | ... |
---|---|---|---|---|---|---|
Data | ... | 이름:namevalue: @5004 | 이름:agevalue:@5005 | ... |
@1003, @ 1004의 값을 보자.
둘다 같은 주소값을 가지고있다.
그렇기에. obj2를 변환시켜도, 해당 값(@5003)은 동일하게 참조하고 있기에,
사본이 변하면 원본도 변하고, 원본이 변하면 사본도 변한다.
let obj= { name: 'mcCommi', age: 25 };
let obj2 = { name: 'mcCommi', age: 25 };
obj2.age = 27;
이렇게 새로 객체를 지속적으로 만드는것은 코드상으로 매우 곤란한 코드가 될것이다.
이전 Lodash를 직접 구현할때 lodash에서 _cloneDeep() 메소드가 있었는데 왜 이런게 있지? 하는 의문점이 있었다.
그런데 이제 이해가 된다.
const source = { a: 1, b: 2 };
const copy = Object.assign({}, source);
console.log(copy); // { a: 1, b: 2 }
const copyObjectDeep = target => {
let result = {};
if (typeof target === 'object' && target !== null) {
for (let prop in target) {
result[prop] = copyObjectDeep(target[prop]);
}
} else {
result = target;
}
return result;
}
undefined 는 JavaScript 엔진이 다음과 같은 경우일때 자동으로 부여한다.
- 선언은 되었지만 값이 할당된 적 없는 변수에 접근할 때
- 존재하지 않는 객체 프로퍼티에 접근할 때
- return이 없거나 호출되지 않는 함수를 실행했을 때 (실행이 안되는것이 아니라, 실행 결과로 undefined를 반환)
undefined는 undefined라는 값이 저장된 것이 아니라,
아직 존재하지 않는 프로퍼티에 접근했음을 알려주는 일종의 Alert이다.
하지만 undefined는 사용자가 직접 변수에 할당이 가능하기 때문에, 없는값을 명시해주기 위해선 왠만하면 null을 사용하는것을 추천한다.
값이 비어잇음을 명시적으로 알려준다.
얕은, 깊은복사 구현이 신기하고 어렵다