복사
어떤 값을 다른 변수에 대입할 때 기존 값과 참조 관계가 끊기는 것
- 객체가 아닌 값은 애초부터 함조 관계가 없으므로 그냥 복사가 된다.
- 객체를 복사할 때는 얕은 복사와 깊은 복사가 있는데 얕은 복사는 중첩된 객체가 있을 때 가장 바깥 객체만 복사되고, 내부 객체는 참조 관계를 유지하는 복사를 의미한다. 깊은 복사는 내부 객체까지 참조 관계가 끊겨 복사되는 것을 의미한다.
참조형 데이터는 저장된 프로퍼티를 복사시 그 주솟값을 복사하여 사본과 원본이 같은 주소를 가리키게 된다. 그렇게 되면, 원본이 바뀌면 사본이 바뀌고 사본이 바뀌면 원본이 바뀌는 문제가 생긴다.
그래서 기본형 데이터는 그대로 복사하면 되지만 참조형은 그내부의 프로퍼티까지 복사해야한다.
여기서 복사
라고 함은 복사본을 수정할 때 원본이 바뀌지 않는 것을 의미한다.
얕은 복사
중첩된 객체가 있을 때 가장 바깥 객체만 복사되고, 내부 객체는 참조 관계를 유지하는 복사
- slice복사
- spread복사(... 연산자를 사용)
- assign복사
Array.prototype.slice()
공식 문서에 slice()에 관해 찾아보면 begin부터 end까지 에 대한 얕은 복사본으로 새로운 배열 객체로 반환한다.
slice()
는 원본을 대체하지 않는다. 원본 배열에서 요소의 얕은 복사본을 반환한다. 라고 적혀있다.
const arr = [ 1, 2, 3, 4, 5]
const copyArr = arr.slice();
checker(arr, copyArr) //true
checker(arr, copyArr) //false
하지만 slice()는 중첩 구조 복사를 제대로 수행할 수 없다는 단점이있다.
const arr = [ 1, 2, 3, [4, 5]]
const copyArr = arr.slice();
checker(arr, copyArr) //true
checker(arr, copyArr) //true
위와 같이 중첩된 구조를 변경하면 원본 배열과 복사된 배열이 모두 영향을 받는다.
Spread 복사
... 연산자를 spread 문법이라고 하는데, spread 문법은 기존 객체의 속성을 새 객체에 할당할 때 사용한다.
배열이라면, [...배열]을 하면 되고, 객체라면 {...객체}를 하면 된다.
const array = [{ j: 'k'}, { l: 'm' }];
const shallowCopy = [...array];; //얕은 복사
console.log(array === shallowCopy); //false
console.log(array[0] === shallowCopy[0]); //true
array와 shallowCopy 변수는 서로 다른데, array[0]와 shallowCopy[0]은 같다. 가장 바깥 객체는 복사되어 참조 관계가 끊어지므로 다른 값이 된다.
Spread 역시 중첩된 구조의 배열은 불변성을 지키지 못하며 복사한다.
Object.assign
const arr = [1, 2, [3, 4]];
const copyArr = Object.assign([], arr);
checker(arr, copyArr); //true
copyArr[2].push(5);
checker(arr, copyArr) //true
Object.assign 역시 중첩구조에서의 불변성을 지키지 못한다.
깊은 복사
내부 모든 값을 하나하나 찾아내어 전부 복사
- 재귀적 방법
- JSON.parse & JSON.stringify
JSON.parse & JSON.stringify
위에서 봤던 방법들은 결국 중첩 구조를 불변성을 지키며 복사하는 것에 대해 실패하였다.
JSON.stringify 를 활용해 데이터를 문자로 변형시키고
JSON.parse를 활용해 다시 문자를 객체 데이터로 변형시킨다.
const arr = [1, 2, [3, 4]];
const copied = JSON.parse(JSON.stringify(arr));
checker(arr, copied); // true
copied[2].push(5);
checker(arr, copied); // false
하지만 JSON은 function, arrow function, undefined는 불가하다.
참고
간단한 객체는 JSON.parse(JSON.stringify(객체))를 사용해도 크게 문제는 없지만 성능이 느리고 함수나 Math, Data 같은 객체를 복사할 수 없다는 단점이 있으므로 실무에서는 lodash(의 '클론') 같은 라이브러리를 사용하곤 한다.
const array = [{j:'k'}, {l:'m'}];
const reference = array; //참조
const shallowCopy = [...array]; //얕은 복사
const deepCopy = JSON.parse(JSON.stringify(array)); //깊은 복사
console.log(array === reference); //true
console.log(array[0] === reference[0]); //true
console.log(array === shallowCopy); //false
console.log(array[0] === shallowCopy[0]); //true
console.log(array === deepCopy); //false
console.log(array[0] === deepCopy[0]); //false
문제
다음 다섯 개의 값을 각각 복사하라. 여기서 복사라고 함은 복사본을 수정할 때 원본이 바뀌지 않는 것을 의미한다. 객체라면 복사한 객체 내부의 값을 바꿔도 원본 객체의 값이 바뀌지 않아야 한다.
const a = 'b';
const c = ['d', true, 1];
const e = { g:'h'};
const i = [{ j: 'k'}, { l: 'm'}];
const n = { o: { p: 'q'}};
정답
const aCopy = a;
const cCopy = c.slice(); //c배열 안에 원시값만 들어있으므로 얓은 복사를 해도 깊은 복사와 동일한 기능을 함
//또는
const cCopy = [...c];
const eCopy = {...e}; //객체 안에 객체가 들어가있는 경우가 아니므로 위와 동일하게 얕은 복사만 해도 됨
const iCopy = JSON.parse(JSON.stringify(i));
const nCopy = JSON.parse(JSON.stringify(n));