얕은 복사와 깊은 복사

리안·2023년 3월 14일
0

1. 원시값 복사 : 값 자체가 새롭게 잘 복사됨 ✅

원시값인 Number, String, Boolean, Null, Undefined는 값을 복사할 때 완전히 다른 메모리에 할당을 하기 때문에 원본과 복사본은 완전히 다른 값이다.

const score = 10;
let copy = score ;
 copy = 20;
console.log(score,copy); //10 20 

위와 같이 복사본인 copy에 값을 20으로 재할당 했음에도 원본인 score의 값은 바뀌지 않았다. 즉, 복사한 값이 원시값이라면 완전 다른 별개의 값이므로 깊은 복사나 얕은 복사의 문제가 나타나지 않는다.

2. 객체 복사(원시값이 아닌 객체) : 얕은 복사가 디폴트 ✅ why? 참조적 특성 때문에!

원시값이 아닌 객체({}, []..)는 어떨까? 이 객체들은 원시값과 달리 참조값을 통해 객체에 접근한다. 객체의 식별자가 가리키는 메모리에는 객체가 아니라 객체가 저장되어 있는 메모리 주소가 있다. 이 참조값을 이용해 실제 객체에 접근할 수 있다. (객체는 이중으로 접근해야 함)

객체는 이러한 특성 때문에 복사하면 기본적으로 얕은 복사가 일어난다. 얕은 복사는 새로운 메모리에 객체를 복사해서 새롭게 가리키는 것이 아닌, 원본 객체의 메모리 주소를 가리키는 새로운 식별자를 만드는 것이다. 그래서 한쪽에서 객체를 변경하면 다른 쪽에서도 영향을 받게 된다.

3.객체의 얕은 복사의 예시

let car = {
  name : 'benz',
  color : 'black',
}
//copyCar는 car를 그냥 복사햇다(얕은 복사)
let copyCar = car;
//변경하면 서로 영향을 받는다. 
copyCar.color = 'red';
console.log(car.color, copyCar.color);
//"red" "red"

(1)Object.assign()

Object.assign()을 쓰면 객체가 얕은 복사된다.

const user3 = Object.assign({},user);
console.log(user3);

Object.assign은 앞에 인수에 {} , 뒤 인수로 적은 객체의 모든 속성을 복사하는 메소드이다.
즉, 객체를 쉽게 복사할 수 있는 메소드이다!

(2) 스프레드 문법

다음과 같이 ...(스프레드)를 통해 복사를 했다. 이 또한 객체를 얕은 복사한 예시이다.

const obj = {x: 1, y : 2};
const copy = {...obj};
console.log(obj === copy); //false

이렇게 스프레드 문법을 사용하여 객체를 복사하면, 복사된 개체는 원본과 다른 객체처럼보인다. 실제로 obj,copy를 비교하면 다른 객체라고 나온다. 하지만 이는 깊은 복사로 보이는 눈속임일 뿐이며, 얕은 복사이다.

이는 예시의 원본객체의 값이 모두 원시값으로 이루어져 있는 객체이기 때문이다. 객체는 복사하면 원본객체와 복사 객체는 완전히 다른 객체이다. 하지만 내부에 있는 객체나 배열이 있는 경우, 복사객체는 이를 원본과 공유하게 된다.

즉, 겉으로 보면 다른 객체같지만 속을 보면(프로퍼티로 갖고 있는 객체나 배열) 같은 객체이다. 그래서 100% 복사된 깊은 복사라고 할 수 없다.

console.log(obj === copy); //false

이 비교는 속이 아닌 겉만 비교하여 t/f을 반환하기 때문에 false라고 나와도 둘은 사실 얕은 복사된 (객체와 배열로 이루어진 내부의 프로퍼티를 공유하는) 객체들이다.

그러나 이 예시는 원본 객체가 원시값만 프로퍼티로 갖는 객체이기에, 이때는 완전히 다른 객체(깊은 복사)라고 봐도 무방하다! 이는 배열을 스프레드로 복사한 경우도 마찬가지이다!

사실 객체의 얕은 복사를 이해하려면 객체안의 객체나 배열이 있는 다음과 같은 예시가 더 낫다.

const obj = {x : 1, 
             y : {
             a : 2, b : 3
            }};
//스프레드 문법으로 복사되었다.
//얕은 복사이다.
const copy = {...obj};
//복사본의 내부 프로퍼티를 바꾸었다.
copy.y.a = 5;
//원본 객체가 영향을 받는다.
console.log(obj);
//{x:1, y : {
// a : 5, b: 3}}

이미지는 다음과 같다. aa,bb를 보면 원시값인 프로퍼티는 새롭게 복사되었으나, 그 내부 프로퍼티는 아직도 공유하고 있는 것을 알수 있다. 스프레드 문법을 쓰면 이렇게 얕은 복사가 된다.

배열을 스프레드로 복사한 경우를 알아보자.

let Arr1 = [1,2,3];
let Arr2 = [...Arr1];
Arr1[1] = 5;
console.log(Arr2[1]) = 2;

이때는 Arr2에서의 변경이 Arr1에 영향을 끼치지 않는다. 왜냐하면, ...로 복사된 값이 []안에 있는 숫자(원시값)이기 때문이다. 이 배열은 계층관계로 이루어지지 않은 ,평범한 배열이기 때문에 깊은 복사(완전히 다른 객체를 만듦)를 했다고 봐도 무방하다.

요약하자면, 객체는 특수하기 때문에 스프레드 문법으로도 깊은 복사가 불가능하다. 스프레드 문법 또한 겉의 원시값만 복사할 뿐 내부의 깊은 프로퍼티까지 새로 만들지는 못한다..그러나 객체 또한 깊은 복사를 할 수 있다.

객체 깊은 복사 - JSON사용

객체의 깊은 복사는 JSON.parse()JSON.stringfy()등 특수한 메소드
를 사용하면 가능하다.

(이 예시는 어려워서 chapGPT한테 물어봤다..ㅎㅎ)

//프로퍼티가 객체로 이루어져 있다.
const originalObj = {
  name: "John",
  age: 30,
  address: {
    city: "New York",
    state: "NY",
    country: "USA"
  }
};

const copiedObj = JSON.parse(JSON.stringify(originalObj));

originalObj.name = "Jane"; //원본 name재할당
originalObj.address.city = "Los Angeles"; //원본 city재할당

console.log(originalObj);
// { name: 'Jane', age: 30, address: { city: 'Los Angeles', state: 'NY', country: 'USA' } }

//이름과 city가 바뀌었다.
console.log(copiedObj); 
// { name: 'John', age: 30, address: { city: 'New York', state: 'NY', country: 'USA' } }

//원본의 이름과 city가 바뀌었음에도 전혀 영향을 받지 않았다. 깊은 복사 성공! 

이렇게 내부의 프로퍼티까지 공유하지 않아야 깊은 복사인 것이다.

3. for...in으로 순회하며 복사

const user = {name : 'rin', age : 20};
const user2 = {};
for(key in user){
  user2[key] = user[key];
}
console.log(user2);

현재는 쓰이지 않지만 참고로 알아두자!


✏️ 심화 : 함수 안에서 매개변수를 통해 객체를 재할당한다면 어떻게 될까?

답은 안된다! 새로운 객체가 매개변수를 통해 생성될 뿐이다.

let person = {name : 'oh'};
function change(obj){
  obj = {name : 'kim'};
  console.log(obj); //{name : 'kim'}
}
change(person) //{name : 'oh'}

name이라는 프로퍼티를 갖고 있는 person 이라는 객체를
객체를 인자로 받아와 새로운 객체 {name : 'kim'}으로 할당해주는 change라는 함수에 넣어보았다.

일어날 수 있는 일은 두 가지이다.
1. person이 {name : 'kim'}으로 재할당된다.
2. person은 변하지 않고 원래 값인 {name : 'oh'}를 유지한다.

답은 2번이다.
왜냐하면 change라는 함수의 인자인 obj라는 이름으로 새로운 객체인 {name : 'kim'}이 생성되기 때문이다.
즉, 원래 객체인 person은 change라는 함수에 넣어다 한들 바뀌지 않는다.
여기서 알 수 있는 것은, 함수의 파라미터에 객체를 할당하면 그 파라미터의 이름으로 '새로운 객체'가 만들어진다는 것이다.

profile
좋은 개발자가 되기 위한 한 걸음

0개의 댓글