원시 자료형을 할당한 변수를 다른 변수에 할당하면 값 자체의 복사가 일어난다. 값 자체가 복사된다는 것은 둘 중 하나의 값을 변경해도 다른 하나에는 영향을 미치지 않는다는 것을 의미한다.
예시
let num = 4; let newNum = num; console.log(num); // 4 console.log(newNum); // 4 console.log(num === newNum); // true newNum = 6; console.log(num); // 4 console.log(newNum); // 6 console.log(num === newNum); // false
반면, 참조 자료형은 임의의 저장공간에 값을 저장하고 그 저장공간을 참조하는 주소를 메모리에 저장하기 때문에 다른 변수에 할당할 경우 값 자체가 아닌 메모리에 저장되어 있는 주소가 복사된다. 참조 자료형이 저장된 변수를 다른 변수에 할당할 경우, 두 변수는 같은 주소를 참조하고 있을 뿐 값 자체가 복사 되었다고 볼 수 없다.
예시
let arr = [0, 1, 2, 3]; let newArr = arr; console.log(arr); // [0, 1, 2, 3] console.log(newArr); // [0, 1, 2, 3] console.log(arr === newArr) // true newArr.push(4); console.log(arr); // [0, 1, 2, 3, 4] console.log(newArr); // [0, 1, 2, 3, 4] console.log(arr === newArr) // true
참조 자료형이 몇 단계로 중첩되어 있던지, 한 단계까지만 복사하는 것을 얕은 복사라고 한다. 아래 설명할 slice(), Object.assign(), spread syntax 등의 방법으로 참조 자료형을 복사하면, 중첩된 구조 중 한 단계까지만 복사가 가능하다.
1) slice()
let arr = [0, 1, 2, 3]; //주솟값 9876 let copiedArr = arr.slice(); //주솟값 1234 console.log(copiedArr); // [0, 1, 2, 3] console.log(arr === copiedArr); // false copiedArr.push(4); console.log(copiedArr); // [0, 1, 2, 3, 4] console.log(arr); // [0, 1, 2, 3]
2) spread syntax
let arr = [0, 1, 2, 3]; //주솟값 9876 let copiedArr = [...arr]; //주솟값 1234 console.log(copiedArr); // [0, 1, 2, 3] console.log(arr === copiedArr); // false copiedArr.push(4); console.log(copiedArr); // [0, 1, 2, 3, 4] console.log(arr); // [0, 1, 2, 3]
새롭게 생성된 배열은 원본 배열과 같은 요소를 갖지만 참조하고 있는 주소는 다르다. 주소가 다르기 때문에 복사한 배열에 요소를 추가해도 원본 배열에는 추가되지 않는다.
1) Object.assign()
let obj = { firstName: "coding", lastName: "kim" }; let copiedObj = Object.assign({}, obj); console.log(copiedObj) // { firstName: "coding", lastName: "kim" } console.log(obj === copiedObj) // false
2) spread syntax
let obj = { firstName: "coding", lastName: "kim" }; let copiedObj = {...obj}; console.log(copiedObj) // { firstName: "coding", lastName: "kim" } console.log(obj === copiedObj) // false
한 단계까지만 복사되고 안에 중첩된 자료형은 같은 주소를 참조하고 있다.
참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사하는 것은 깊은 복사(deep copy)라고 한다. 그러나 JavaScript 내부적으로는 깊은 복사를 수행할 수 있는 방법이 없다. 단, JavaScript의 다른 문법을 응용하면 깊은 복사와 같은 결과물을 만들어 낼 수 있다.
JSON.stringify()는 참조 자료형을 문자열 형태로 변환하여 반환하고, JSON.parse()는 문자열의 형태를 객체로 변환하여 반환한다. 먼저 중첩된 참조 자료형을 JSON.stringify()를 사용하여 문자열의 형태로 변환하고, 반환된 값에 다시 JSON.parse()를 사용하면, 깊은 복사와 같은 결과물을 반환할 수 있다.
const arr = [1, 2, [3, 4]]; const copiedArr = JSON.parse(JSON.stringify(arr)); console.log(arr); // [1, 2, [3, 4]] console.log(copiedArr); // [1, 2, [3, 4]] console.log(arr === copiedArr) // false console.log(arr[2] === copiedArr[2]) // false
하지만 이 방법을 사용하면 함수가 null로 바뀌게 된다. 따라서 이 방법은 완전한 깊은 복사 방법이라고 보기 어렵다.
node.js 환경에서 외부 라이브러리인 lodash, 또는 ramda를 설치하면 깊은 복사가 가능하다.