얕은복사와 깊은복사

유진·2023년 7월 31일
0

얕은 복사와 깊은 복사를 알기 전 불변객체에 대해 알아보자.

1. 객체는 불변성이 아니라, 가변성이지 않나?

맞습니다. 객체는 가변성 데이터 타입입니다.

2. 객체를 왜 불변객체로 만들어야할까?

객체를 복사해, 새로운 객체로 만든 뒤 값을 변경하면

let obj = {a:1, b:1, c:2};
obj1 = obj;

obj1.a = 30;
console.log(obj1.a, obj.a) // 30, 30

이런식으로, 기존의 obj 의 a 값도 같이 변경되는 모습을 확인할 수 있습니다.

이러한 현상을 방지하기 위해, 복사를 제대로 한 뒤 새로운 객체를 만들어야합니다.
(기존 데이터를 건들지 않고, 새롭게 복사한 객체의 값만 수정되도록 함)

얕은 복사를 사용해서 불변 객체 만들기

얕은 복사란?
바로 아래 단계의 값만 복사함.
(복사본이 원본의 속성과 동일한 참조(메모리주소)를 공유하는 복사)

1. 기존 데이터를 복사해 새로운 객체를 반환하는 함수

let copyObject = function(target){
	let result = {};
  for (let prop in target){
  	result[prop] = target[prop];
  }
  return result;
}

for in 문법을 사용해 새로운 result 객체에 target 객체의 프로퍼티를 복사하는 함수

var user = {
  name: "yujin",
  gender: "male",
};

var user2 = copyObject(user);
user2.name = "jin";

if (user !== user2) {
  console.log("유저 정보가 변경되었습니다."); // 유저 정보가 변경되었습니다.
}

console.log(user.name, user2.name); // yujin jin
console.log(user === user2); // false

이러한 방식을 사용하면
프로토타입 체이닝 상의 모든 프로퍼티를 복사하는 점과,
getter/setter는 복사되지 않는 점,
얕은 복사만을 수행하는다는 점의 단점이 있습니다.

위와 같은 함수를 사용했을 때 (얕은복사) 생기는 문제

let copyObject = function(target){
	let result = {};
  for (let prop in target){
  	result[prop] = target[prop];
  }
  return result;
}

let user ={
  name: "yujin",
  urls: {
    port : "http://portfolio",
    blog: "http://blog",
    instar: "http://instar"
  }
}

let user2 = copyObject(user);

user2.name = "jin";
console.log(user.name === user2.name) // false

user.urls.port = "http://port";

console.log(user.urls.port === user2.urls.port) // true

얕은 복사를 통해, 객체에 속한 프로퍼티도 복사가 되지만,
기존 데이터를 참조하고 있는 모습을 확인할 수 있습니다.

참조를 하고 있다면 변경이 된다면 참조하고 있는 복사 객체에게도 값이 영향이 간다는 뜻입니다.

이러한 현상이 발생하지 않게 하려면 내부 프로퍼티에 대해서도 불변 객체로 만들어야합니다.
-> 깊은 복사를 한다.

깊은 복사를 사용해 불변 객체 만들기

1. 기존 데이터를 복사해 새로운 객체를 반환하는 함수

let copyObject = function(target){
	let result = {};
  for (let prop in target){
  	result[prop] = target[prop];
  }
  return result;
}

let user ={
  name: "yujin",
  urls: {
    port : "http://portfolio",
    blog: "http://blog",
    instar: "http://instar"
  }
}

let user2 = copyObject(user);
user2.urls = copyObject(user.urls);

user.urls.port = "http://port1";

console.log(user.urls.port === user2.urls.port) // false

객체 내부의 프로퍼티까지 함수를 사용해 복사를 하여 새로운 데이터로 만든 결과, 내부 프로퍼티까지 참조가 되지 않는 복사가 잘 된 모습을 확인할 수 있습니다.

하지만 참조형 데이터가 있을 때마다 재귀적으로 수행해야만 깊은 복사가 된다는 점이 참 불편합니다.

간단하게 깊은 복사하는 방법 2가지

1. JSON 문법

const copyObject = (target) => {
  return JSON.parse(JSON.stringify(target))
}

let obj = {
  a: 1,
  b: {
    c: null,
    d: [1,2],
    func1 : () => {console.log(3)}
  }
}

let obj2 = copyObject(obj);

obj2.a = 3;
obj2.b.c = 4;

console.log(obj.a, obj2.a) // 1, 3 
console.log(obj.b.c, obj2.b.c) // null, 4
console.log(obj.b.func1, obj2.b.func1) // () => {console.log(3)}, undefined

JSON 문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꾸는 방법입니다.

하지만 메서드(함수)나 숨겨진 프로퍼티는 모두 무시합니다.
그래서 http 요청으로 받은 데이터를 저장한 객체를 복사할 때 등 순수한 정보만 다룰 때 사용하기 좋은 방법입니다.

2. Lodash 라이브러리 사용

Lodash의 cloneDeep 사용

var objects = [{ 'a': 1 }, { 'b': 2 }];
 
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

객체나, 배열 안에 불변값만 있는 경우에는 얕은 복사만 해도 괜찮다.

let arr = [1,2,3]
let obj = {a: 1, b:"string"}
let arr1 = [...arr]
let obj1 = {...obj}
profile
욕심꾸러기개발자

0개의 댓글