- 원시 자료형(primitive data type)과 참조 자료형(reference data type)의 구분이 왜 필요한지 이해할 수 있다.
- 원시 자료형과 참조 자료형의 차이를 이해하고, 각자 맞는 상황에서 사용할 수 있다.
- 원시 자료형이 할당될 때는 변수에 값(value) 자체가 담기고, 참조 자료형이 할당될 때는 보관함의 주소(reference)가 담긴다는 개념을 코드로 설명할 수 있다.
- 참조 자료형은 기존에 고정된 크기의 보관함이 아니라, 동적으로 크기가 변하는 특별한 보관함을 사용한다는 것을 이해할 수 있다.
- 참조 자료형인 값을 복사하는 방법에 대해서 이해한다.
JavaScript에서 자료형(type)이란 값(value)의 종류이다. 크게 원시 자료형과 참조 자료형 두 가지로 나뉘며, 각각의 자료형은 고유한 속성과 메서드를 가지고 있다.
number
, string
, boolean
, undefined
, null
과 같이 고정된 저장 공간을 차지하는 자료형
변수를 선언하면 그 변수 이름의 사물함 하나가 주어지고, 거기에 값을 할당하면 사물함 안에 값이 들어가는 것!
변수를 호출하게 되면, 변수이름을 가진 사물함을 찾아서 그 안에 있는 값을 반환한다.
이렇듯 원시 자료형은 각각의 사물함 공간을 가지고 있어서 값 복사 시 사물함 안의 데이터를 복사한 복사본을 자신의 사물함에 넣기 때문에, 값을 수정해도 원본 데이터는 수정되지 않는다 (불변 값; immutable value)
array
, object
function
처럼 대량의 데이터를 다루기 적합한 자료형(원시 자료형이 아닌 자료형은 전부 참조 자료형이다!)
원시 자료형처럼 대량 데이터를 사물함으로 하나하나 줄 경우 삭제, 추가 등이 매우 복잡해진다
따라서 어떤 부분의 사물함을 시원하게 밀어버리고 특별한 저장 공간 heap
을 만듬
참조 자료형을 선언하게 되면, 사물함에 변수 이름을 붙이고, 데이터를 할당할 경우엔 사물함의 안에는 데이터가 아닌, 그 데이터가 있는 heap
의 주소가 들어가게 된다
즉, 사물함은 그 주소를 가리키고 있는 것!
변수를 호출하게 되면, 그 이름의 사물함을 찾고, 그 안에 적힌 주소로 찾아가 주르륵 연결된 대량의 데이터를 반환하는 것! (이를 "참조한다(refer)" 라고 함)
참조 자료형은 원시 자료형과 달리 데이터 자체를 복사하는 것이 아니라 주소를 복사해 간다. 따라서 각각 다른 변수라도 같은 주소를 가리키고, 데이터 내용을 변경하면 그 주소에 있는 데이터 값이 변경되는 것이기 때문에 영향을 받게 된다
(가변값; mutable value)
그렇다면, 배열과 객체 같은 참조 자료형을 복사하여, 똑같은 요소와 프로퍼티를 가지지만 원본과 복사본이 서로 영향을 미치지 않도록 할 수는 없을까?
let arr = [0, 1, 2, 3];
let copiedArr = arr.slice();
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
slice는기존 배열의 데이터 값 자체를 똑같이 복사해서 '새로운' 배열을 생성한다.
이는 원본 배열과 같은 요소를 갖지만, 참조하고 있는 주소는 다르다.
ES6에서 새롭게 추가된 문법으로, spread라는 단어의 뜻처럼 배열을 펼칠 수 있다.
...
을 붙여주면 펼칠 수 있다. let arr = [0, 1, 2, 3];
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
🚨얕은 복사는 참조 자료형이 몇 단계로 중첩되어 있던지, 한 단계까지만 복사할 수 있다🚨
➡ 즉, 참조 자료형 내부에 참조 자료형이 중첩되어 있는 경우, slice()
,Object.assign()
, spread syntax
사용해도 참조 자료형 내부에 참조 자료형이 중첩된 구조는 복사할 수 없다.
참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사하는 것.
BUT, JavaScript 내부적으로는 깊은 복사를 수행할 수 있는 방법이 없다.
➡ 다른 문법을 응용해서 해야 함
JSON.stringify()
와 JSON.parse()
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
를 설치
const lodash = require('lodash');
const arr = [1, 2, [3, 4]];
const copiedArr = lodash.cloneDeep(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
const ramda = require('ramda');
let arr = [1, 2, [3, 4]];;
let copiedArr = ramda.clone(arr);
사진 출처: 코드스테이츠