Frontend/JS.info 정리

객체: 기본 - 참조에 의한 객체 복사

Creative_Lee 2021. 12. 13. 16:04

객체와 원시타입의 가장 큰 차이점은

원시 타입이 '값 그대로' 저장되고 복사되는 반면에

객체는 '참조에 의해' 저장되고 복사된다는 것입니다.

 

참조에 의한 값 복사 ( 객체 )

 

문자열을 할당한 변수를 복사한 결과입니다.

let msg = "hi"
let phrase = msg

phrase = "hello"

console.log(msg)	// hi 
console.log(phrase)	// hello

각자 다른 문자열이 출력 되었습니다.

 

객체를 할당한 변수를 복사한 결과입니다.

let msg = { msg : 'hi'}

let phrase = msg

phrase.msg = 'hello'

console.log(msg)	// {msg: 'hello'}
console.log(phrase)	// {msg: 'hello'}

기존의 변수도 값이 변경되어 같은 객체가 출력됩니다.

 

위의 예시처럼 객체는 메모리 내 어딘가에 저장되고,

변수에는 객체를 '참조'할 수 있는 값이 저장됩니다.

객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않습니다.

 

객체 = 서랍장 = 1개 ,

변수 = 서랍장 열쇠 = 2개

라고 생각합시다! 

 

참조에 의한 비교

객체 비교 시 동등 연산자( == )와 일치 연산자( === )의 결과는 동일합니다.

비교하려는 대상 객체가 서로 같은 객체를 참조할 경우 true를 반환합니다.

 

let msg = { msg : 'hi'}

let phrase = msg

console.log(msg == phrase)	// true
console.log(msg === phrase)	// true

같은 객체를 참조하기 때문에 true 입니다!
let msg = { msg : 'hi'}
let phrase = { msg : 'hi'}

console.log(msg == phrase)	// false
console.log(msg === phrase)	// false

객체의 내용만 보면 같아 보이지만, 서로 독립된 객체이기 때문에 참조값은 서로 다릅니다.

 

Deep copy란?

우리가 객체를 복사하여 값을 수정할 때! 그저 참조값을 복사한 것 뿐이라면.....

복사본 수정 시 원하지 않게 원본 객체의 데이터도 수정하게 됩니다.

따라서 우리는 Deep copy( 깊은 복사 )를 통해 원본 객체의 수정을 막아야 합니다.

 

그렇다면... 객체를 원시타입처럼  Deep copy( 깊은 복사 )하고 싶다면 어떻게 해야 할까요~?

기존 객체와 똑같으면서도 독립적인 객체를 원한다면 말이에요!

 

 

1. for...in문 

다음과 같이 for...in 문을 활용해서 모든 프로퍼티를 복사할 수 있습니다만...

let info = {
  isGood : true,
  isPretty : false,
}

let clone = {}

for(let key in info){
  clone[key] = info[key]
}

clone.isGood = false	//  복사 완료 후 객체의 값 변경!

console.log(info)	// {isGood: true, isPretty: false} 원본은 그대로~
console.log(clone)	// {isGood: false, isPretty: false} 변경 완료 !

조금 cool 하지 못하네요...

 

 

 

2. Object.assign 

 

Object.assign 활용하면 cool 하게 객체 deep copy를 할 수 있습니다.

let info = {
  isGood : true,
  isPretty : false,
}

let clone = {}

Object.assign(clone,info)

clone.isGood = false	//  복사 완료 후 객체의 값 변경!

console.log(info)	// {isGood: true, isPretty: false} 원본은 그대로~
console.log(clone)	// {isGood: false, isPretty: false} 변경 완료 !

Object.assign( 목표 객체 , 복사할 객체 , 복사할 객체 2 ... 복사할 객체 n ) 의 방법으로 이루어 집니다.

 

Object.assign()은 동작 후 목표 객체를 리턴하기 때문에

위의 코드를 조금 더 간단하게 줄일 수 있습니다.

let clone = Object.assign({},info)

 

목표 객체의 프로퍼티와 동일한 이름의 프로퍼티가 복사할 객체에 있다면 ?

기존값에 덮어쓰기 합니다!

let info = {
  isGood : true,
  isPretty : false,
  isImportant : false, 
}

let clone = {
  isGood : false,
}

Object.assign(clone, info)


console.log(info.isGood)	// true
console.log(clone.isGood)	// true

 

3. spread operator

 

spread operator 활용하면 더욱더 cool 하게 객체 deep copy를 할 수 있습니다.

let info = {
  isGood : true,
  isPretty : false, 
}

let clone = {...info}

clone.isGood = false	// 복사 완료 후 객체의 값 변경!

console.log(info)	// {isGood: true, isPretty: false} 원본은 그대로~
console.log(clone)	// {isGood: false, isPretty: false} 변경 완료 !

 

spread operator를 통한 객체 복사 시 에도

목표 객체의 프로퍼티와 동일한 이름의 프로퍼티가 복사할 객체에 있다면 ?

기존값에 덮어쓰기 합니다!

let info = {
  isGood : true,
  isPretty : false, 
}

let clone = { 
  isPretty : true, 
}

clone = {...info}

console.log(info)	// {isGood: true, isPretty: false} 
console.log(clone)	// {isGood: true, isPretty: false} 덮어쓰기 되었습니다 ㅠㅠ

 

중첩 객체의 Deep copy

위에서 알아본 객체 복사에서는

복사할 객체의 프로퍼티가 전부 원시값이었습니다.

그렇다면 프로퍼티가 또 다른 객체인 경우에는 어떻게 해야할까요~?

 

생각보다 쉽지 않습니다.

 

 

 

쉽지 않은 중첩 객체의 Deep copy

1. for..in문으로 Deep copy 시도!

let info = {
  boy : {
    isGood : true,
    isPretty : false,
  },
  girl : {
    isGood : true,
    isPretty : true,
  }
}

let clone = {}

for(let key in info) {
  clone[key] = info[key]
}

clone.boy.isGood = false	// 복사 완료 후 객체의 값 변경!

console.log(info.boy.isGood)	// false  원본 객체의 값도 바뀌어 버렸습니다 ㅠㅠ
console.log(clone.boy.isGood)	// false

실패했습니다.

 

 

2. Object.assign 으로 Deep copy 시도!

let info = {
  boy : {
    isGood : true,
    isPretty : false,
  },
  girl : {
    isGood : true,
    isPretty : true,
  }
}

let clone = Object.assign({}, info)

clone.boy.isGood = false	//  복사 완료 후 객체의 값 변경!

console.log(info.boy.isGood)	// false 원본 객체의 값도 바뀌어 버렸습니다 ㅠㅠ
console.log(clone.boy.isGood)	// false

실패했습니다.

 

 

3. spread operator로 Deep copy 시도!

let info = {
  boy : {
    isGood : true,
    isPretty : false,
  },
  girl : {
    isGood : true,
    isPretty : true,
  }
}

let clone = {...info}

clone.boy.isGood = false	//  복사 완료 후 객체의 값 변경!

console.log(info.boy.isGood)	// false 원본 객체의 값도 바뀌어 버렸습니다 ㅠㅠ
console.log(clone.boy.isGood)	// false

실패했습니다.

 

어떻게 보면 당연한 결과이기도 합니다.

위의 3개의 코드에서는 info 객체만 deep copy 했을 뿐,

객체 속 프로퍼티의 객체는 shallow copy( 얉은 복사 ) 해버렸기 때문입니다.

 

 

이 문제를 해결해 봅시다.

 

 

커스텀 재귀 함수로 해결

재귀 함수를 만들어서 해결할 수 있습니다.

function deepCopy(obj){
  if (obj === null || typeof obj !== 'object'){
    return obj;
  } 

  let clone = {};

  for (let key in obj) {
    clone[key] = deepCopy(obj[key]);
  }

  return clone;
}

let info = {
  boy : {
    isGood : true,
    isPretty : false,
  }
}

let clone = deepCopy(info)	// deepCopy 재귀함수 실행!

clone.boy.isGood = false	// 복사본 값 변경!

console.log(info.boy.isGood)	// true 원본은 그대로!
console.log(clone.boy.isGood)	// false 복사본만 변경되었습니다!

하지만 중첩 객체의 Deep copy를 위해 상기 코드를 작성하는 것은 효율이 상당히 떨어집니다.

 

 

여기서 아주 매력적인 라이브러리 하나를 소개합니다.

 

Lodash의 _cloneDeep() 사용하기

import _ from 'lodash'

let info = {
  boy : {
    isGood : true,
    isPretty : false,
  }
}

let clone = _.cloneDeep(info);  // lodash 라이브러리의 cloneDeep 함수!

clone.boy.isGood = false	// 복사본 값 변경!

console.log(info.boy.isGood)	// true 원본은 그대로~
console.log(clone.boy.isGood)	// false 복사본만 변경~

위와 같이 lodash 라이브러리의 cloneDeep() 를 사용해 아주 짧은 코드로 아주 간편하게

중첩 객체의 Deepcopy를 할 수 있습니다.

 

 

 

 

기본이 중요하다.

'Frontend > JS.info 정리' 카테고리의 다른 글

객체: 기본 - method 와 this  (0) 2021.12.19
객체 : 기본 - 가비지 컬렉션  (0) 2021.12.16
객체: 기본 - 객체  (0) 2021.12.09
코드 품질 - 바벨( Babel )  (0) 2021.12.09
코드 품질 - 테스트 자동화  (0) 2021.12.07