JavaScript에서 자료형(type)이란 값(value)의 종류를 말하는데 각각의 자료형에는 고유한 속성과 메서드를 가지고 있다.
자료형은 크게 두 가지로 구분할 수 있는데 원시 자료형(primitive type)과 참조 자료형(reference type)이 있다.
원시 자료형이 아닌 모든 자료형은 참조 자료형으로 여러 데이터를 다룰 수 있는 배열, 객체가 대표적인 참조 자료형이고, 함수도 참조 자료형으로 분류된다.
primitive type | reference type |
---|---|
원시 자료형을 변수에 할당하면 메모리 공간에 값 자체가 저장 | 참조 자료형을 변수에 할당하면 메모리 공간에 주솟값이 저장 |
원시 값을 갖는 변수를 다른 변수에 할당하면 원시 값 자체가 복사되어 전달 | 참조 값을 갖는 변수를 다른 변수에 할당하면 주솟값이 복사되어 전달 |
원시 자료형은 변경 불가능한 값(immutable value), 한 번 생성된 원시 자료형은 읽기 전용(read only) 값 | 참조 자료형은 변경이 가능한 값(mutable value) |
원시 자료형을 할당한 변수를 다른 변수에 할당하면 값 자체의 복사가 일어난다. -> 값 자체가 복사되는 것은 둘 중 하나의 값을 변경해도 다른 하나에는 영향을 미치지 않는다는 것을 의미한다.
let word = 'hello';
let copyWord = word;
console.log(word); // hello
console.log(copyWord); // hello
console.log(word === copyWord); // true
word = 'javascript';
console.log(word); // javascript
console.log(copyWord); // hello
console.log(word === copyWord); // false
참조 자료형은 임의의 저장공간에 값을 저장하고, 그 저장공간을 참조하는 주소를 메모리에 저장한다. 그렇기 때문에 다른 변수에 할당할 경우 값 자체가 아닌 메모리에 저장되어 있는 주소가 복사된다.
let arr = [1, 2, 3, 4, 5];
let copyArr = arr;
console.log(arr); // (5) [1, 2, 3, 4, 5]
console.log(copyArr); // (5) [1, 2, 3, 4, 5]
console.log(arr === copyArr); // true
둘 중 하나를 변경했을 때 해당 변수가 참조하고 있는 주소에 있는 값이 변경되기 때문에 다른 하나에도 영향을 미친다.
-> 참조 자료형이 저장된 변수를 다른 변수에 할당할 경우, 두 변수는 같은 주소를 참조하고 있을 뿐, 값 자체가 복사되었다고는 볼 수 없다.
배열, 객체와 같은 참조 자료형을 복사하여 똑같은 요소와 프로퍼티를 가지지만 원본과 복사본이 서로 영향을 미치지 않도록 하는 방법에 대해 살펴볼 것이다.
배열을 복사하는 방법에는 크게 두 가지가 있다.
slice()
를 사용하는 방법 spread
를 사용하는 방법
배열 내장 메서드인 slice()
를 사용하여 원본 배열을 복사할 수 있다.
새롭게 생성된 copyArr 배열은 원본 배열과 같은 요소를 가지고있지만 참조하고 있는 주소는 다르다.
let arr = [1, 2, 3];
let copyArr = arr;
console.log(arr); // [1, 2, 3]
console.log(copyArr); // [1, 2, 3]
console.log(arr === copyArr); // true
spread syntax는 ES6에서 추가된 문법으로 변수명 앞에 ...
을 붙여서 사용할 수 있다.
let arr = [1, 2, 3];
console.log(...arr); // 1 2 3
같은 요소를 가진 배열을 각각 만든 변수에 할당했을 때 두 변수는 참조 자료형이기 때문에 서로 다른 주소를 참조한다.
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false
spread를 사용하면 어떨까? 새로운 배열 안에 원본 배열을 펼쳐서 전달하면 원본 배열과는 같은 요소를 갖고 있지만, 각각 다른 주소를 참조하게 되기 때문에 결과적으로 slice()
메서드를 사용한 것과 동일하게 작동한다.
let arr = [1, 2, 3];
let spreadArr = [...arr];
console.log(arr); // (3) [1, 2, 3]
console.log(spreadArr); // (3) [1, 2, 3]
console.log(arr === spreadArr); // false
spreadArr.push(100);
console.log(arr); // (3) [1, 2, 3]
console.log(spreadArr); // (4) [1, 2, 3, 100]
객체를 복사하기 위해서는 Object.assign()
을 사용한다.
let obj = { name: "hoho", age: 20 };
let copyObj = Object.assign({}, obj);
console.log(copyObj) // { name: "hoho", age: 20 }
console.log(obj === copyObj) // false
spread syntax는 배열뿐만 아니라 객체를 복사할 때도 사용 가능하다.
let obj = { name: "hoho", age: 20 };
let copyObj = {...obj};
console.log(copyObj) // { name: "hoho", age: 20 }
console.log(obj === copyObj) // false
참조 자료형 내부에 참조 자료형이 중첩된 경우 slice()
, Object.assign()
, spread syntax
를 사용해도 참조 자료형 내부에 참조 자료형이 중첩된 구조는 복사할 수 없다.
딱 1단계까지만 복사가 가능하기 때문에 깊은 복사는 불가능하다.
참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사하는 것을 깊은 복사라고 하는데 JavaScript의 다른 문법을 응용하여 깊은 복사와 같은 결과물을 만들어 낼 수 있다.
JSON.stringify()
는 참조 자료형을 문자열 형태로 변환하여 반환JSON.parse()
는 문자열의 형태를 객체로 변환하여 반환위의 두 가지를 사용하여 깊은 복사와 같은 결과물을 만들어 낼 수 있다.
JSON.stringify()
를 사용하여 문자열의 형태로 변환한다.JSON.parse()
를 사용하여 깊은 복사와 같은 결과물을 반환한다.const obj = {
name: 'hoho',
age: 20,
arr: [1, 2, [3, 4, 5]]
}
const copyObj = JSON.parse(JSON.stringify(obj));
console.log(obj); // {name: 'hoho', age: 20, arr: Array(3)}
console.log(copyObj); // {name: 'hoho', age: 20, arr: Array(3)}
console.log(obj === copyObj); // false
console.log(obj.arr[2] === copyObj[2]); // false
중첩된 참조 자료형 중에 함수가 포함되어 있을 경우, JSON.stringify()
+ JSON.parse()
를 사용했을 때 함수가 null
로 바뀌는 문제가 발생한다.
그래서 이 방법또한 완벽하게 깊은 복사 방법이 될 수 없다.
const arr = [1, 2, [3, function(){ console.log('hello')}]];
const copyArr = JSON.parse(JSON.stringify(arr));
console.log(arr); // [1, 2, [3, function(){ console.log('hello')}]]
console.log(copyArr); // [1, 2, [3, null]]
console.log(arr === copyArr); // false
console.log(arr[2] === copyArr[2]); // false
완전하게 깊은 복사를 해야 되는 경우 lodash, ramda 외부 라이브러리를 통해 깊은 복사를 이용할 수 있다고 한다.