데이터 복사(copy)에도 깊이가 있다?

Ken·2023년 1월 1일
1

Javascript

목록 보기
1/1

인간은 망각의 동물이다. 특정한 행동이나 생각을 자주 하지 않으면 우리 뇌 속에서는 신경의 가지치기 현상이 일어난다고 한다. 우리가 어떤 생각, 기분, 행동을 수행하지 않으면 뇌는 그와 연관된 뉴런들을 발화시키지 않는데, 그 결과 그러한 생각, 기분, 행동이 점점 약해지다가 결국 시들어버리고 잊게 되는 것을 신경 가지치기라고 한다. 그렇기 때문에 우리는 초등학교 시절처럼 리코더를 연주할 수 없는거고, 곡면의 부피를 구하는 적분 공식이나 근의 공식등에 대해 더는 기억하지 못하는 것이다. 쉽건 어렵건 지금 나는 무조건 많이 생각하고 자꾸 써보고 사용해보는 것 밖에 방도가 없다 생각한다.

 

이 내용은 자바스크립트 객체를 공부하던 중 아래 내용으로 부터 시작되었다.

객체는 메모리를 효율적으로 사용하기 위해, 그리고 객체를 복사해 생성하는 비용을 절약하여 성능을 향상시키기 위해 객체는 변경 가능한 값으로 설계되어 있다. 메모리 사용의 효율성과 성능을 위해 어느 정도의 구조적인 단점을 감안한 설계라고 할 수 있는데, 이러한 구조적 단점에 따른 부작용이 있다. 그것은 원시 값과는 다르게 여러 개의 식별자가 하나의 객체를 공유할 수 있다는 것이다.

 
알다시피, 자바스크립트의 자료형은 큰 범주로 "원시(Primitive)타입과 참조(Reference)타입이 있는데, 이 둘의 가장 큰 차이점 중 하나는 바로 원시타입은 할당된 값 그대로 저장되는 반면, 참조타입은 할당된 값이 아닌 해당 값이 저장되어 있는 메모리 주소의 참조 값이 저장된다.

 

원시 타입 값의 저장의 예

let hello = "Hello"; let copyHello = hello;
console.log(hello);
console.log(copyHello);

 
이는, 실질적인 객체 값은 메모리 내 어딘가에 저장이되고, 변수 user에는 객체를 '참조'할 수 있는 값이 아닌 해당 값이 들어 있는 실질적인 메모리 주소가 저장된다. 한마디로 객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 실질적인 객체는 복사되지 않는다.

 

참조 타입 값의 저장의 예

 

let user = { name: 'Kaya' }; 
let admin = user; // 또 다른 변수인 admin을 생성 후 기존 참조 타입의 user 변수를 복사했다.

 
이는 아래 그림처럼 변수는 두개이지만 각 변수에는 동일 객체에 대한 참조 값이 저장이 되는 것이다.

 

그렇기 때문에, 객체에 접근하거나 객체를 조작할 땐 여러 변수를 통해 사용할 수 있다.

 

let user = { name: 'Kaya' }; 
let anotherUser = user; 
anotherUser.name = 'Jane'; // 'anotherUser' 참조 값에 의해 변경 됨 
console.log(user.name); // 'Jane'이 출력된다.. 'user' 참조 값을 이용해 변경사항을 확인하고 적용 됨

 
이와 관련해서 가장 많이 쓰이는 예시가 있다. 객체를 서랍장에 비유하면 변수는 서랍장을 열 수 있는 열쇠라고 할 수 있는데, 서랍장은 하나, 서랍장을 열 수 있는 열쇠는 두개인데, 그 중 하나(변수 anotherUser)를 사용해 서랍장을 열어 새로 정리하고, 또 다른 열쇠(변수 user)로 서랍장을 열면 정리된 새로운 내용을 볼 수 있다.

 

그래서, 객체를 비교 시 참조에 의한 비교가 발생되기 때문에 동등 연산자인 == 와 일치 연신자 ===는 동일하게 동작이 되는 것이다.

 

let oldSchool = {}; 
let newSchool = oldSchool; // 참조를 통한 복사 
console.log( a == b ); // true, 두 변수는 같은 객체를 참조한다. 
console.log( a === b ); // true

 
하지만, 두 객체 모두 비어 있는 점에서 같아 보이겠지만, 서로 독립된 객체이기 때문에 비교하면 거짓이 반환된다.

 

이는 위에서 설명된 내용처럼, 두 변수다 빈 객체를 값으로 가지고 있지만, 해당 값이 아닌 해당 값이 들어있는 서로 다른 메모리 주소를 참조하고 값으로 들어 있기 때문에 거짓이 반환되는 것이다.

let hamburger = {}; 
let spaghetti = {}; 
console.log( a == b ); // false 
console.log( a === b ); // false

 

 
여기까지 서론이다. 👻💀👽

자, 본론으로 들어가서 복사를 하면??

 
객체가 할당된 변수를 복사하면 동일한 객체에 대한 참조 값이 하나 더 만들어 진다는건 알았다, 그러면 객체를 복사하고 싶다면? 그것도 기존 객체 값과 똑같으면서도 독립적인 객체를 만들고 싶다면? 여러가지 방법은 있지만 기본적으로 해당 기능을 수행하는 자바스크립트 Built-in 메서드는 없다.

 

그렇기 때문에 객체를 복사하기 위해선 새로운 객체를 만들고 기존 객체의 프로퍼티들을 반복문을 통해 순회해서 일일히 복사를 해도 되고, Object.assign이라는 메서드를 사용하는 방법도 있다고 한다.

 

반복문을 통해 일일히 순회해서 값을 일일히 복사하는 법

let user = { name: "Kaya", age: 20 }; 
let cloneUser = {}; // 새로운 빈 객체 생성 // 빈 객체에 user 프로퍼티 전부를 복사해 넣는다.
for (let key in user) { cloneUser[key] = user[key]; } // 이제 cloneUser은 완전히 독립적인 복제본이 되었다. 
cloneUser.name = "Jane"; // cloneUser의 데이터를 변경한다.
console.log( user.name ); // 기존 객체에는 여전히 Kaya가 있습니다. 

 

Object.aasign 메소드를 사용하여 값을 복사하는 법

 

let user = { name: "Kaya", age: 20 }; 
let cloneUser = Object.assign({}, user);

 

사실 난 여기서 굉장히 뿌듯해했고, 모든 게 다 끝났다고 생각했다...하지만 중첩 객체를 복사해야 한다면?? 🤔

 
위의 작업은 모두 중첩 객체가 아닌 경우에만 해당한다.

 

let user = { name: "Kaya", sizes: { height: 165, weight: 52 } }; 
let cloneUser = Object.assign({}, user); 
console.log( user.sizes === cloneUser.sizes ); 
// true, 네 같은 객체입니다. 
// user와 cloneUser는 sizes를 공유한다. 
user.sizes.width++; // 한 객체에서 프로퍼티를 변경한다. 
console.log(cloneUser.sizes.weight); // 53,

 
다른 객체에서 변경 사항을 확인할 수 있다;;;;;

 

복사는 복사인데 이걸 "얕은 복사(Shallow Copy)" 라고 한다.

그리고 깊은 복사(Deep Copy)라는 것도 있다.

정리하면 아래와 같다.

 

얕은 복사(Shallow Copy): 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계 depth까지만 복사하는 것
깊은 복사(Deep Copy): 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것

 

그렇기 때문에 애시당초 내가 원했던 것은 얕은 복사가 아닌 깊은 복사였기 때문에, 현재는 아래의 방법을 통해 진행해야 한다.

  1. 객체의 구조까지 복사해줄 수 있도록 반복문을 통해 일일히 객체 값을 복사

  2. 자바스크립트 lodash라는 라이브러리에 있는 _.cloneDeep(obj)라는 메서드를 통해 별도의 알고리즘을 구현하지 않고 깊은 복사 사용

 

처음에 너무 이해가 안가서 관련 내용을 검색 하다 좋은 외제(?) 포스팅이 있어 해당 저자에게 관련 내용을 내가 포스팅할 수 있는지 며칠전에 메시지를 보냈지만, 아직까지도 그는 답변이 없다.

그가 잘 지내는지 걱정...된
농담이고, 그래서 관련 링크를 참고 링크에 두었다.

 
 
참고
https://javascript.plainenglish.io/learn-deep-shallow-copy-with-tricky-javascript-questions-c563cdb5a4dd

0개의 댓글