얕은 복사(Shallow copy)
객체를 복사할 때 원래 값과 복사된 값이 같은 참조를 가리키고 있는 것을 말한다.
const user = {
// key: value
name: 'jeong',
age: 85,
email: ['gyflsakfn@naver.com']
}
const copyUser = user
console.log(copyUser === user)
user.age = 22
console.log('user', user)
console.log('copyUer', copyUser)
console.log('------')
console.log('------')
copyUser와 user는 true가 나왔기 때문에 같은 메모리 주소를 바라보고 있다.
user의 객체 데이터만 수정 했을 뿐인데 copyUser의 데이터까지 수정되는 문제가 발생했다.
이것이 얕은 복사에 의한 것이다. 두 변수는 똑같은 참조를 가리키고 있기 때문이다.
= 키워드에 의해 얕은 복사가 된다는 것을 알았다. 다른 얕은 복사 방법을 알아보자.
Array.prototype.slice()
start부터 end 인덱스까지 기존 배열에서 추출하여 새로운 배열을 리턴하는 메소드이다.
기존 배열에는 영향을 끼치지 않아서 깊은 복사로 보일 수 있지만, 원시 값을 저장한 1차원 배열일 뿐이다.
원시 값은 불변성을 가지고 있기에 깊은 복사를 나타내며, slice()메소드 자체는 얕은 복사를 한다.
const origin = [
[1,2,3],
[0,0,0],
[2,2,2]
];
const copy = origin.slice(); // 복사
console.log(JSON.stringify(origin) === JSON.stringify(copy)) // true
// 복사된 배열만 수정
copy[0][0] = 50;
copy[2].push(7);
console.log(origin) // [[50, 2, 3], [0, 0, 0], [2, 2, 2, 7]]
console.log(copy) // [[50, 2, 3], [0, 0, 0], [2, 2, 2, 7]]
1차원 배열이 아닌 중첩 구조를 가지고 2차원 배열로 얕은 복사임을 증명했다.
const origin = [
{
name: 'jeong',
age: 20,
},
true
];
const copy = origin.slice(); // 복사
// 복사된 배열만 수정
copy[0].name = 'min';
copy[1] = false;
console.log(origin)
console.log(copy)
배열 안의 객체를 수정하고자 할 경우 얕은 복사를 수행하고, 원시 값은 불변성을 가지기에 깊은 복사가 이루어졌음을 알 수 있다.
Object.assign()
const copyUser = Object.assign({}, user)
console.log(copyUser === user) // false
user의 데이터를 대상 객체가 받아서 새로운 객체 데이터가 새로운 메모리 주소에 할당 된다. 객체 자체는 다른 객체지만 그 안의 값은 기존 object안의 값과 같은 참조 값을 가리킨다.
Spread 연산자
const copyUser = {...user}
user.emails.push('kjm@naver.com')
console.log(user.emails === copyUser.emails) // true
user객체의 emails라는 배열 데이터에다가 push메소드를 사용하였다. push는 배열 데이터 가장 뒷 부분에 새로운 아이템으로 인수를 밀어 넣는다. 즉 user의 emails에 인수를 밀어 넣는다. emalis는 배열 데이터이며 참조형 데이터이다. emalis라는 참조형 데이터를 따로 복사 처리 한 적은 없다. user라는 객체 데이터 하나만 복사 처리 하였기 때문에 emails 배열 데이터는 같은 메모리 주소를 공유하고 있다. 얕은 복사는 표면만 복사하기 때문에 속의 내용은 복사 되지 않아서 깊은 복사를 통해 참조형 데이터 내부로 들어가서 모든 데이터들을 복사처리 해야 한다.
깊은 복사(Deep copy)
깊은 복사된 객체는 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체를 말한다.
JSON.parse && JSON.stringify
JSON.stringity 함수를 이용하여 Object 전체를 문자열로 변환한 뒤 다시 JSON.parse 함수를 이용하여 문자열을 Object 형태로 변환한다. 문자열로 변환하는 과정에서 객체에 대한 참조가 사라지며 새로운 객체로 깊은 복사가 가능하다.
var origin = [{ name: "jeong", age: 50 }];
var copy = JSON.parse(JSON.stringify(origin[0]));
copy.name = "min";
copy.age = 25;
console.log("origin :", origin); // name: jeong, age:50
console.log("copy :", copy); // name: min, age:25
lodash를 활용한 깊은 복사
import _ from 'lodash' // _기호가 하나의 객체 데이터
const user = { // 참조형 데이터(객체)
// key: value
name: 'jeong',
age: 85,
emails: ['gyflsakfn@naver.com'] // 또 다른 참조형 데이터(배열)
}
const copyUser = _.cloneDeep(user) // 깊은 복사(clone:복제)
console.log(copyUser === user)
user.age = 22
console.log('user', user)
console.log('copyUer', copyUser)
console.log('------')
console.log('------')
user.emails.push('kjm@naver.com')
console.log(user.emails === copyUser.emails)
console.log('user', user)
console.log('copyUser', copyUser)
깊은 복사를 순수한 JS로직으로 만들기엔 복잡하기 때문에 lodash의 cloneDeep을 이용하여 깊은 복사를 해보았다. user의 emails에 새로운 배열이 push된 것을 확인할 수 있다.
cloneDeep
This method is like _.clone except that it recursively clones value.
이 방법은 값을 재귀적으로 복제한다는 점을 제외하고는 _.clone과 비슷하다.
재귀: 하나의 데이터 안에서 어떠한 내용이 반복적으로 실행되는 것을 의미한다. 반복 실행 하면서 모든 값들을 복제한다.
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
objects의 배열데이터 []껍데기만 복사한 것이 아니라 내부에 들어있는 객체 데이터까지 복사하여 새로운 메모리로 할당하였기 때문에 false가 나온 것 이다.
정리하자면, 대표적 참조형 데이터인 객체나 배열을 복사할 때는 그 내부에 또 다른 참조데이터가 없다는 전제하에는 얕은 복사를, 특정한 참조형 데이터에 또 다른 참조형 데이터가 들어가져 있다면 깊은 복사를 고려해야 한다.
lodach cloneDeep
'개발자의 공부 > JS' 카테고리의 다른 글
[JS문법] 일급 객체 (0) | 2022.12.26 |
---|---|
[JS]생성자 함수에 의한 객체 생성 (0) | 2022.12.19 |
[JS]데이터 불변성(Immutability) (1) | 2022.09.29 |
[JS]스코프(Scope) (2) | 2022.09.26 |
[JS]async & await - 직관적인 비동기 처리 코드 (0) | 2022.09.22 |