[JS] 객체 복사

MJ·2022년 9월 4일
0

Java Script

목록 보기
32/57
post-thumbnail

참조에 의한 객체 복사

  • 객체를 제외한 나머지 자료형(원시형)들은 자신을 복사하면 원시값 그대로 복사가 됩니다.

  • 객체는 객체를 복사하면, 객체를 참조할 수 있는 메모리 주소값 이 저장 됩니다. (참조 값)

원시값이란, 객체를 제외한 나머지 자료형들의 데이터를 말합니다. (문자,숫자 등)


1.1 객체 (원시값) 복사

let message = "Hello";
let phrase = message;	
/* 
1. phrase 변수에 message 변수를 할당
2.`message` 변수가 갖고 있는 원시값이("Hello") 복사 된다. 
*/


1.2 객체 생성 과정

let user = { 
  name: "John"
}

/*
1. 메모리상 어딘가에 { } 빈 객체가 생성되고 user 변수는 빈 객체에 접근할 수 있는 참조값이 저장됩니다.
2. 빈 객체에 키와 쌍으로 이루어진 프로퍼티 name을 생성합니다. (값 : 'john')
*/

1.3 객체 복사 과정


let user = { name: "John" };
let admin = user;				

/*
1. 객체를 생성하고 user 변수에 참조값이 저장됩니다. { name: "John" }
2. admin 변수에 user 변수가 접근할 수 있는 객체에 대한 주소값을 저장합니다.
3. admin 변수 또한 user 변수와 같은 객체에 대한 주소값을 공유 받습니다.
*/


1.4 객체 수정

변수가 서로 같은 객체를 참조한다면, 객체를 수정하면 다른 변수도 수정된 객체를 참조합니다.

let user = { name: "John" };
let admin = user;

admin.name = "Pete";	
alert(user.name);	// 출력 : Pete

/* 
1. user 변수와 admin 변수는 같은 객체를 공유합니다.
2. admin 변수를 통해서 객체에 접근해 name 프로퍼티의 값을 수정합니다 ( john -> pete )
3. 같은 객체를 공유하므로, user 변수에서 name 프로퍼티에 접근하면 수정된 값을 공유 받습니다.
*/

객체를 서랍장에 비유하면 변수는 서랍장을 열 수 있는 열쇠라고 할 수 있습니다.
위 예시에서 서랍장(user)은 하나이고, 열쇠는 2개 입니다. (user, admin)
하나의 열쇠로 서랍장을 열어서 무언가 수정하면, 다른 열쇠도 수정된 서랍장을 볼 수 있다.



참조에 의한 비교

  • 객체를 비교할 때 동등, 일치 연산자는 동일하게 작동합니다.

  • 비교 시 피 연산자인 두 객체가 동일한 객체인 경우에 참을 반환합니다.


let a = {};		// 객체 생성 > a 변수가 참조함
let b = a;		// b 변수도 a 변수가 참조하는 객체를 참조함 ( 즉 a와 b는 같음 )

alert( a == b );	// true
alert( a === b );	// true


/* 
1. 빈 객체를 생성하고 a 변수에 참조 값 저장
2. b 변수도 a 변수가 참조하는 객체에 대한 참조 값을 공유 받습니다.
3. 동일한 객체를 공유하므로 두 변수는 값이 동일합니다.
*/

2.1 다른 객체간의 비교

let a = {};
let b = {};
alert ( a == b );	// false, 두 변수는 서로 참조하는 객체가 다름


/* 
1. 빈 객체를 생성하고 a 변수에 주소 저장
2. 빈 객체를 생성하고 b 변수에 주소 저장
3. a와 b는 같은 객체를 공유하는 것이 아니라, 서로 다른 빈 객체를 생성한 것이므로
값이 같지 않습니다.

쉽게 예를 들어 디자인이 동일한 두 지갑에 만원이 있다고 해서 그 지갑이 같은 지갑인 것은 아닌것 처럼요
*/

🔔 객체 원시형 비교

obj1 > obj2 같이 비교연산을 하거나, obj == 5 같은 원시형과 객체를 비교하면
객체도 원시형으로 변환합니다.



객체 자체 복사

  • 객체를 복사하면, 이 객체를 참조할 수 있는 주소값이 복사됩니다.

  • for..in 반복문을 사용하면 객체 자첼르 복사해서 공유가 아닌 서로 다른 독립적인
    객체를 생성할 수 있습니다.

🔔 객체를 복사하려면?

객체를 구성하고 있는 프로퍼티를 원시수준으로 만들고, 프로퍼티를 복사하면 됩니다.
즉, 객체에 모든 프로퍼티가 원시값으로 그대로 복사가 됩니다.


let user = {
 name: "John",
 age: 30
};


let clone = {};	

for (let key in user) {
  clone[key] = user[key];	// user 객체의 모든 프로퍼티를, clone 객체에 할당합니다. 
}

clone.name = "Pete";	// clone.name 값을 변경
alert(user.name);		// 출력 : John


/* 
1. for..in 반복문을 통해서 user 객체에 대한 모든 데이터를 clone 객체에 복사합니다.
2. 참조 값이 아니라 원시 자료형 값 자체를 복사 했기에 서로 다른 독립적인 객체가 생성 됩니다.
3. clone 객체에서 user 객체와 동일한 name 프로퍼티에 대한 값을 수정하면 user 객체의
name 프로퍼티는 변경되지 않는 것을 확인할 수 있습니다.

4. 두 객체는 아래와 같이 서로 다른 객체로 확인할 수 있습니다.
  clone { name: "Pete" }; 
  user { name: "John" };
*/

3.1 Object.assign (얕은 복사)

  • Object.assign 메서드를 사용해서 손쉽게 객체를 복사하는 방법이 있습니다.

  • for..in 반복문보다 강력하고, 효과적입니다.

object.assign(dest, [src1, src2,...]);
     
                     
/* 
(dest, [src1, src2,...])
1. dest 인수는 복사한 객체를 할당할 객체입니다.
2. 첫 번째 인수를 제외한 인수들은, 복사대상이 되는 객체들 입니다.
3. src1, src2에 해당하는 객체들이 dest라는 객체에 복사 됩니다.
4. 복사가 완료되면, dest 객체를 반환합니다.

❗ 복사 대상이 되는 객체의 개수는 상관 없다.
*/

/* Object.assign 사용 */

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

Object.assign(user, permissions1, permissions2);
// permissions1,2 객체를 복사해서 user 객체에 붙여 넣는다.

user = {
 name: "John",
 canView: true,
 canEdit: true
};
// 다른 객체들의 모든 프로퍼티가 복사되었습니다.

3.2 얕은 복사 도중, 프로퍼티 이름이 중복되는 경우

  • 복사 하려는 대상과 그 값을 할당받을 객체에 동일한 프로퍼티 키가 있는 경우에는
    복사 대상의 프로퍼티 키로 덮어 씌워집니다.

let user = { name: "John" };

Object.assign(user, { name: "Pete" });

alert(user.name);	// 출력 : "Pete"
// user 객체의 name 프로퍼티가 덮어 씌워졋다.

🔔 얕은 복사는, 중첩객체를 복사할 수 없습니다.



중첩 객체 복사

  • 얕은 복사나 for..in 반복문은 객체의 프로퍼티가 원시값만 저장한 경우에서 복사했습니다.

  • 프로퍼티가 다른 객체의 참조 값을 가지고 있는 경우에는 어떻게 되는지 살펴보겠습니다.


let user = {	
 name: "John", 
  
 sizes: {		// user.sizes 객체 ( 이 객체는, user 객체 안에 sizes 프로퍼티가 참조 한다 )
  height: 182,
  width: 50
 }
};

alert( user.sizes.height );	// 출력 : 182


/* 
clone = { }; 빈 객체가 있다고 가정하고, 이 객체가 user.sizes 객체를 복사한다고
예시를 두겠습니다.

(1)
user가 참조하는 객체에는 { name: "John" }; 이라는 원시형 프로퍼티가 있습니다.
원시형이기 때문에 그대로 clone 객체에 복사할 수 있습니다.

(2) 
user 객체 안에 프로퍼티를 보니 sizes는 다른 객체를 참조하고 있습니다. (중첩 객체)
sizes라는 프로퍼티는 값으로 다른 객체의 참조 값을 가지고 있지요.

(3)
clone 객체가 user.sizes 프로퍼티를 복사하면, sizes가 참조하고 있는 객체의 참조 값을
복사하게 됩니다. 즉, (clone.sizes == user.sizes)이 됩니다. 
clone과 user는 다른 객체지만 sizes라는 객체만은 같이 공유하는 객체가 되는 것이지요.


❗ (clone.name != user.name) = true,  (clone.name.sizes == user.name.sizes) = true
*/

4.1 중첩 객체를 얕은 복사로 복사하면?

  • 얕은 복사나 for..in 반복문으로는 중첩 객체 자체를 복사할 수 없습니다.

  • 복사가 아닌, 중첩 객체를 참조할 수 있는 주소 값이 복사 됩니다.


let user = {
  
 name: "John",
 sizes: {
  height: 182,
  width: 50
 }
  
};

let clone = Object.assign( {}, user );

alert( user.sizes === clone.sizes );	// true, 같은 객체입니다
// 즉, user객체와 clone객체는 sizes객체를 공유합니다.

user.sizes.width++;			// user.sizes에 프로퍼티 width 값을 변경합니다. (후위증가)
console.log(clone.sizes.width);	// 51, 객체를 공유하기 때문에 clone에서 변경사항을 확인할 수 있습니다.

⚠️ 중첩 객체는 어떻게 복사합니까?

이 문제를 해결하려면 user의 각 프로퍼티마다 값을 검사하면서, 그 값이 객체인 경우
객체의 구조도 복사해주는 반복문을 사용해야 합니다. 이런 방식을 깊은 복사
deep cloning 라고 부릅니다.

깊은 복사시 사용되는 표준 알고리즘을 사용하면 이 알고리즘을 직접 구현하지 않아도
깊은 복사를 처리할 수 있습니다.
표준 알고리즘 참조

자바스크립트 라이브러리 lodash의 메서드인 _.cloneDeep(obj)를 사용하면 이 알고리즘을
구현하지 않아도 깊은 복사를 처리할 수 있습니다.

clonedeep(obj)


4.2 중첩 객체 복사하기

  • _.cloneDeep(obj) 메서드를 사용해서 객체안에 객체(중첩객체)를 참조 값이 아니라
    객체 자체를 복사할 수 있습니다.

  • 중첩 객체에 대한 참조 값이 아니라, 중첩 객체 안에 존재하는 모든 프로퍼티를 다른
    객체로 복사해서 사용할 수 있다.

_.cloneDeep(obj) 메서드를 사용하려면 라이브러리 lodash를 다운받아야 합니다.

❗ cdn 사용 ( 브라우저에서 호출할 때 사용 하세요 )
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>​
javascript를 출력하는 html 문서 파일에 복사해줍니다.
❗ npm 다운로드
npm i -g npm
npm i --save lodash	
❗ node.js 사용하기
var _ = require('lodash');

let user = {
  
 name: "John",
 sizes: {
  height: 182,
  width: 50
 }
  
};

let clone = _.cloneDeep(user)			// user 객체의 모든 프로퍼티를 복사합니다.

alert( user.sizes === clone.sizes );	// false, 두 객체는 sizes를 공유하지 않습니다.

user.sizes.width++;			// user.sizes에 프로퍼티 width 값을 1 증가 
alert(clone.sizes.width);	// 50, 서로 다른 객체이므로 프로퍼티를 공유하지 않습니다.



정리

객체는 참조에 의해서 할당되고 복사됩니다. 변수에는 객체 자체가 아니라, 객체를
참조하는 메모리상의 주소가 저장됩니다. 즉, 객체가 할당된 변수를 복사하거나 함수의
인자로 넘길 때는 객체가 아니라 객체를 참조하는 값이 복사된다.


복사된 참조값을 사용하면, 모든 프로퍼티에 대한 작업이 공유 됩니다.


객체 자체를 복사하려면, 얕은 복사(shallow copy)를 사용하거나, 깊은 복사(deep cloning)
를 사용하면 됩니다.

얕은 복사 : Object.assign( )
깊은 복사 : lodash 라이브러리에 ._cloneDeep() 메서드


출처 : https://ko.javascript.info/object-copy

profile
프론트엔드 개발자가 되기 위한 학습 과정을 정리하는 블로그

0개의 댓글