고정된 공간 콜스택(call stack)에 저장되는 자료형으로 number, string, boolean, null, undefined 등이 이에 속한다.
주소값은 고정된 공간 콜스택(call stack)에 저장되지만 데이터 값은 힙(heap) 에 저장되는 자료형으로 배열, 객체, 함수 등이 이에 속한다.
변수 할당시 메모리 공간에 값 자체가 저장된다.
다른 변수에 할당시 원시값 자체가 복사된다.
재할당시 콜스택 내의 최초 선언된 값은 불변(immutable value)하며새로운 공간을 확보한 뒤 그 공간에 재 선언후 재할당값을 부여한다. ( 문자열에서 인덱스로 접근은 가능하지만 변경은 불가한 이유가 이 때문이다.)
남아있는 최초 할당값은 js엔진이 자동으로 메모리에서 삭제를 하며 이를 갈비지 콜랙터 (garbage collector) 라고 한다. ( 갈비지 콜렉터가 언제 구동할지는 예측이 불가하다.)
변수 할당시 메모리 공간에 주솟값이 저장, 데이터 값은 힙에 저장되며 주솟값을 통해 데이터 값에 접근한다. ( 이를 참조한다(refer)라고 한다.)
다른 변수에 할당시 주솟값이 복사된다.
데이터 값 변경시 변경이 가능하다. (mutable value)
참조 자료형의 단순 복사시 데이터 값이 아닌 주소값이 복사 되어지기 때문에 복사본의 데이터 값을 수정해도 원본의 데이터 값 또한 수정되는 현상을 겪으며 이를 방지할 수 있는 몇가지 복사 방법이 있다.
배열
slice()
javascript 내장 메소드인 slice를 사용 하여 복사하면 참조하는 주소값이 다르게 복사된다.ex) let arr = [2, 3, 5, 6]; let newArr = arr.slice(); newArr[2] = 25; console.log(newArr); // [2, 3, 25, 6] console.log(arr); // [2, 3, 5, 6]
spread syntax
배열이 할당된 변수명 앞에 ... 을 붙여 쓰며 배열을 펼치는 형식이다. 출력시 각 요소를 출력하며 이를 응용해 빈 배열 안에 펼친 요소들을 붙여넣어 참조하는 주소값이 다르지만 요소는 같은 배열로 만들 수 있다.ex) let arr = [2, 3, 5, 6]; let newArr = [...arr]; newArr[2] = 25; console.log(newArr); // [2, 3, 25, 6] console.log(arr); // [2, 3, 5, 6]
객체
Object.assign()
javascript 내장 메소드인 Object.assign을 사용하여 복사하면 참조하는 주소값이 다르게 복사된다.ex) let obj = {a:2, b:3, c:4}; let newObj = Object.assign({}, obj); newObj.a = 7; console.log(newObj); // {a:7, b:3, c:4} console.log(obj); // {a:2, b:3, c:4}
spread syntax
배열과 마찬가지로 변수명 앞에 ... 을 붙여 쓰며 객체를 펼치는 형식이다. 출력시 각 요소를 출력하며 이를 응용해 빈 객체 안에 펼친 요소들을 붙여넣어 참조하는 주소값이 다르지만 요소는 같은 객체로 만들 수 있다.ex) let obj = {a:2, b:3, c:4}; let newObj = {...obj}; newObj.a = 7; console.log(newObj); // {a:7, b:3, c:4} console.log(obj); // {a:2, b:3, c:4}
참조하는 주소값을 다르게 복사하는 방법에는 얕은 복사와 깊은 복사로 나눌 수 있으며 위의 방법들을 통해 참조하는 주소값을 다르게 복사 할 수 있지만 완전한 복사가 가능하지는 않은 얕은 복사방법이다.
얕은 복사 (swallow copy)
얕은 복사란 참조 자료형 내부에 참조 자료형이 중첩되어 있을 시 한단계 까지만 복사가 가능한 방법을 말하며 한단계는 참조하는 주소값이 다르게 복사되지만 중첩되어 있는 참조자료형은 여전히 같은 주소값을 지닌다.
ex) let arr = [{a:2, b:3, c:4},{d:5, e:6, f:8}]; let newArr = arr.slice(); console.log(arr === newArr); // false console.log(arr[0] === newArr[0]); // true
깊은 복사 (deep copy)
깊은 복사란 참조 자료형 내부에 중첩되어 있는 모든 참조자료형이 복사 가능한 방법을 말하며 javascript 내부적으로는 깊은 복사가 수행 가능한 방법이 없다. 그러나 javascript의 다른 문법을 응용해서 깊은 복사와 같은 결과물을 만들 수 있다.
JSON.stringify() 와 JSON.parse()
JSON.stringify()는 참조 자료형을 문자열 형태로 변환하여 반환하며 JSON.parse()는 문자열 형태를 객체로 변환하여 반환한다. 중첩된 참조자료형에 이를 순차적으로 사용해 깊은 복사와 같은 결과물을 반환할 수 있다.ex) let arr = [1, 2, [3, 4]]; let newArr = JSON.parse(JSON.stringify(arr)); console.log(arr); // [1, 2, [3, 4]] console.log(newArr); // [1, 2, [3, 4]] console.log(arr === newArr) // false console.log(arr[2] === newArr[2]) // false
하지만 이 방법은 함수가 포함되어 있을시 null 로 바꾸어 반환하기 때문에 주의해야 하며 완전한 깊은 복사 방법이라고 보기는 어렵다.
ex) let arr = [1, 2, [3, function(){ console.log('hi')}]]; let newArr = JSON.parse(JSON.stringify(arr)); console.log(arr); // [1, 2, [3, function(){ console.log('hi')}]] console.log(newArr); // [1, 2, [3, null]]
외부 라이브러리 사용
완전한 깊은 복사를 해야하는 경우, node.js 환경에서 lodash나 ramda등 깊은 복사를 구현할 수 있는 외부 라이브러리를 설치후 사용하면 된다.//외부 라이브러리인 lodash의 cloneDeep을 사용한 깊은 복사의 예시 ex) let lodash = require('lodash'); let arr = [1, 2, [3, 4]]; let newdArr = lodash.cloneDeep(arr); console.log(arr); // [1, 2, [3, 4]] console.log(newArr); // [1, 2, [3, 4]] console.log(arr === newArr) // false console.log(arr[2] === newArr[2]) // false