원시형(primitive)과 다른 객체형의 특징
=> 참조에 의해 저장되고 복사된다
(primitive는 '값' 그대로 저장,복사됨)
변수에 객체를 담을 땐,
객체 내부의 내용이 변수에 담기는 것이 아닌,
객체가 저장된 메모리 주소, 객체의 참조 값
이 변수에 담긴다.
let user = { name: "John" };
let admin = user;
위와 같은 예시에서 다음과 같이 실행된다.
1. {name : "John"}
이라는 객체가 메모리에 저장됨.
(그 메모리 주소(참조값)는 abc
라고 하자.)
2. 변수 user에 abc
가 담긴다
3. 변수 user에 담긴 abc
가 복사되어 변수 admin에 담긴다.
이 원리를 잘 이해해야만 다음의 예제가 이해될 것이다.
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete';
alert(user.name); // 'Pete'가 출력됨.
admin
의 name
을 Pete로 설정한 것인데,
user
의 name
을 출력했더니 왜 Pete가 나올까?
위에서 처음 생성해준대로 John이 나와야하지 않을까?
이것이 바로 '값 그대로의 복사'와 '참조'의 차이이다.
앞선 설명에서 user 변수와 admin 변수 모두
{name:'John'}
객체의 메모리주소(참조값)인 abc
를 담고 있다고 했다.
그럼
//1번
admin.name = 'Pete';
해당 코드가 실행되면,
admin 변수가 담고있는 abc
값의 주소를 찾아가, 그 abc 메모리에 저장되어있는 객체 내부의 name
의 값을 'Pete'로 변경한다.
//2번
alert(user.name);
해당 코드가 실행되면,
user 변수가 담고있는abc
값의 주소를 찾아가, 그 abc 메모리에 저장되어있는 객체 내부의 name
의 값을 읽어온다.
이 때, abc 메모리에 저장되어있는 객체에서 name의 값은 1번 코드가 실행됐을 때 'Pete'로 설정되었기 때문에 Pete가 get 되는 것이다.
==
, ===
모두 동일하게 동작한다. let a = {};
let b = a; // 참조에 의한 복사
alert( a == b ); // true, 두 변수는 같은 객체를 참조합니다.
alert( a === b ); // true
a와 b 모두 빈 객체{}
의 메모리 주소(참조값)을 가지기 때문에
동일한 객체를 참조하고 있으므로 비교 시 모두 true이다.
let a = {};
let b = {}; // 독립된 두 객체
alert( a == b ); // false
a와 b가 가리키는 두 객체는 서로 동일한 내용, 즉 빈 객체이지만, 다른 메모리 주소(참조값)을 가지고 다른 공간에 저장되어있는 독립된 객체이기 때문에 다른 것으로 취급된다.
참조값을 복사하는 것이 아닌, 객체를 복제 즉 객체의 내용 그대로를 복사하여 독립된 새로운 객체를 만들고자 할 땐 어떻게 할까?
let user = {
name: "John",
age: 30
};
let clone = {}; // 독립된 빈 객체 생성
// user 객체의 각 프로포티의 원시값들을 하나하나 복사하여 추가한다.
for (let key in user) {
clone[key] = user[key];
}
이렇게 복제할 경우, user 객체와 clone 객체는 별도의 객체가 되고,
따라서 user 객체의 속성을 변경하여도, 이것이 clone 객체에 적용되지 않는다.
user.name = "Pete"; // user 객체의 name 값 변경
alert( clone.name ); // John 출력
user 객체의 name 값을 변경해도, 그것이 clone 객체의 name 값엔 아무런 영향을 끼치지 않기 때문에 원래 값인 John이 출력된다.
메소드를 활용하여도 객체의 복제가 가능하다.
Object.assign(목표 객체, [복제 대상 객체1, 복제 대상 객체2, 복제 대상 객체3])
위와 같은 형태처럼
가 가능하기 때문에,
이 되는 것이다.
또 이때, 목표 객체는 빈 객체일 필요 없다.
let user = { name: "John" }; //목표 객체 user
let permissions1 = { canView: true }; //복제 대상 객체 1
let permissions2 = { canEdit: true }; //복제 대상 객체 2
// permissions1과 permissions2의 프로퍼티를 user로 복사합니다.
Object.assign(user, permissions1, permissions2);
위의 코드가 실행되면
user 객체는
기존 properties + permissions1의 properties + permissions2의 properties
={ name: "John", canView: true, canEdit: true }
가 된다.
Object.assign({},복제 대상 객체);
property 값이 원시값이 아닌 또다른 객체라면?
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
이럴 경우, sizes 키의 값도 {height:182, width:50}
이라는 객체의 참조값을 담는다.
따라서
clone.name = user.name;
이 경우엔 clone이라는 별도의 객체에 name: "John"
이 담기게 될 것이다.
하지만
clone.sizes = user.sizes;
의 경우, user sizes가 담고 있는, {height:182, width:50}
이라는 객체의 참조값이 clone 객체의 sizes 키의 값으로 복사되는 것이기 때문에,
clone과 user 각각의 sizes 는 모두 같은 {height:182, width:50}
이라는 객체를 가리키게 되는 것이다.
따라서 객체 내부의 객체 내용까지 모두 복제하고자 할 경우엔,
user 객체의 각 property를 복사하는 반복문 내부에서
객체를 만날 경우 또한번의 반복문으로 user 객체의 property 객체 내부의 property 까지 일일이 복사해야 완벽한 clone이 가능하다.
💡 이렇게 객체 내부의 객체 property까지 모두 그대로 복제하는 것을 "deep cloning (깊은 복사)" 라고 한다.
=
를 사용하여 변수에 객체를 대입하면, 값(내용)이 담기는 것이 아닌, 해당 객체의 메모리 주소가 담긴다.