목차
들어가기전에! 자바스크립트 엔진은 가상머신으로 돌아가는데 버츄얼 머신 메모리 모델에는 힙과 콜스택 영역으로 나눠져 있습니다. (힙: 참조타입, 콜스택: 원시타입)
1. 원시값, 참조값 이란? 그리고 복사가 필요한 이유
1.1 원시값이란?
원시값은 기본자료형을 의미합니다. 예: Number, String, Boolean, Null, undefined
변수에 원시값을 저장하면 변수의 메모리 공간에 실제 데이터 값이 저장됩니다.
1.2 원시값은 어떻게 작동하는가?
자바스크립트에서 원시타입을 선언(declare)하면 이는 stack에 저장됩니다.
* stack이란 LIFO(last in first out: 나중에들어온 순서대로 나간다) 구조를 가진 자료구조 입니다. 저장된 원시타입은 식별자 를 통해서 접근할 수 있고 원시 데이터와 함께 스택에 저장됩니다.
[아래 사진]
자바스크립트 원시타입은 변수에 할당될때, 메모리의 고정 크기로 원시값을 저장하고 해당 저장된 값을 변수가 직접적으로 가르키는 형태를 띕니다.
또한 값이 절때 변하지 않는 불변성을 갖고 있기 때문에 재할당 시 메모리에 재할당 값이 저장되고 변수가 가리키는 메모리가 달라집니다. 여기서 이제 아무대도 쓰이지 않는 메모리는 가비지 컬렉터가 정리하게 됩니다.
let a = 100;
let b = a;
a = 50;
console.log(a); // 50;
console.log(b); // 100;
let a = 100은 오른쪽 그림과 같이 처음에 메모리영역 1에 저장됩니다. 그다음 let b = a 는 메모리 영역2에 저장됩니다.
a = 50은 위에 설명과 같이 원시타입 불변성 으로 새로운값을 재할당 하면 새로운 메모리영역인 메모리영역3에 저장됩니다. 그렇기 때문에 b 변수의 값은 영향을 받지 않습니다. 그리고 메모리영역 1은 사용이 불필요해 졋으므로 가비지 컬렉터가 정리합니다.
1.3 참조값이란??
자바스크립트에서 원시타입을 제외한 나머지는 참조타입(객체(Object)라 할수 있습니다.
예를들어 array, function,object등등 ..
원시타입과 가장 큰 차이점은 변수의 크기가 동적으로 변한다는 것 입니다.
이러한 특징때문에 Object의 데이터 자체는 별도의 메모리공간(heap)에 저장되며, 변수에 할당시 데이터에 대한 주소(heap메모리의 주소값)이 저장되므로 자바스크립트 엔진이 변수가 가지고 있는 메모리 주소를 이용해서 변수의 값에 접근을 합니다.
const object1 = {
name: 'test',
age: 1
}
const object2 = object1;
object1.age = 50;
console.log(object2.age); // 50;
원시값이라면 object1에 대한 업데이트는 object2에 영향이 없을 텐데
위와 같은 경우는 힙 영역에 새로운 영역이 추가되지 않습니다.
콜스택에는 따로 쌓여있지만 같은 heap 메모리 주소를 바라보고 있기 때문입니다.
1.4 참조타입 변수 사용시 복사가 필요한 이유
참조 타입의 변수는 실제 데이터가 저장된 주소를 참조하기에 참조(reference)타입이라고 불리는 것입니다.
그렇기에 참조타입은 서로에게 영향을 줄 수 있으므로 변수의 복사나 수정시 참조 여부를 잘 고려해야합니다.
그렇기 때문에 복사가 필요할 수 있는 것입니다.
2. 얕은 복사 정의 및 방법 (Shallow Copy)
얕은 복사란? 객체를 복사 할 때 기존 값과 복사된 값이 같은 참조를 가리키고 있는 것을 말합니다.
객체 안에 객체가 있을 경우 한개의 객체라도 기존 변수의 객체를 참고하고 있다면 이를 얕은 복사라고 합니다.
얕은 복사 후 해당 변수를 재사용하여 수정한다면 원본 값이 동시에 변하므로 주의가 필요합니다.
그럼 얕은 복사를 하는 방법에 대해서 알아봅시다~
2.1 일반적인 복사
let original = {name: 'joy'}
let copyOriginal = original;
copyOriginal.name = 'whoareu';
console.log(original.name); // whoareu;
console.log(original === copyOriginal);
2.2 Object.assign()
object.assign()을 이용하면 객체 자체는 깊은 복사가 수행되지만, 2차원 이상의 객체는 얕은 복사가 수행됩니다.
아래 코드와 같이 객체 origin, copy = {} 는 콜스택 내부에 서로 다른 위치에 저장되어있지만 객체의 내부에 위치한 오브젝트는 같은 heap 주소를 참조하고 있기 때문입니다.
/* Object.assign(생성할 객체, 복사할 객체) */
let origin = {
a: 1,
b: { c: 2 }
};
//복사진행
let copy = Object.assign({}, origin);
//1차원은 깊은 복사가 수행됨
copy.a = 3;
console.log(origin.a); //a
//2차원 이상은 얕은 복사가 수행됨
copy.b.c = 3
console.log(origin.b.c); //3
console.log(origin === copy) // false
console.log(origin.b.c === copy.b.c) // true
2.3 전개구문 {...}
전개구문도 Object.assign()과 마찬가지로 let origin = {} 객체 자체 1단계는 깊은 복사 이지만 객체 내부의 객체는 위와 같이 같은 힙 주소를 참조하고 있기 때문에 얕은 복사가 진행됩니다.
let origin = {
a: 1,
b: { c: 2 }
};
let copy = {...origin}
//깊은 복사 결과
copy.a = 4;
console.log(origin.a); // 1;
//얕은 복사 결과
copy.b.c = 3
console.log(origin.b.c); //3
console.log(origin === copy) // false
console.log(origin.b.c === copy.b.c) // true
3. 깊은 복사 (Deep copy)
3.1 재귀 함수를 통한 깊은 복사
const origin = {
name : 'test',
age : 30,
address : {
city : "jeju"
}
}
const deepCopy = (obj) => {
if(typeof obj !== "object" || obj === null){
return obj;
}
const deepCopyObj = {};
for(let key in obj){
deepCopyObj[key] = deepCopy(obj[key]);
}
return deepCopyObj;
}
const copy = deepCopy(origin);
copy.address.city = 'seoul';
console.log(origin === copy); //false
console.log(origin.address.city === copy.address.city); //false
3.2 JSON.parse()와 JSON.stringify()를 이용한 깊은 복사
let origin = {
a: 1,
b: { c: 2 }
};
let copy = JSON.parse(JSON.stringify(origin));
copy.b.c = 3
console.log(origin.b.c); // 2
console.log(origin.b.c === copy.b.c) //false
참고: https://velog.io/@nomadhash/Java-Script-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC%EC%99%80-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC, https://hwani.dev/js-primitive-reference-types/
잘못된 정보에 대한 피드백은 언제나 환영입니다 (´▽`ʃƪ)♡
'내직업은 IT종사자 > javascript' 카테고리의 다른 글
[javascript] javascript engine 이벤트 루프(Event Loop) 와 틱(tick) (0) | 2023.06.05 |
---|---|
[javascript] javascript는 메모리관리가 어떻게 될까? (feat. 메모리 누수 사례) (0) | 2023.06.04 |
[javascript] 유사배열객체(Array-like Objects) 란? 대체 무엇인가.. (0) | 2023.04.18 |
[javascript] 배열 생성하는 여러가지 방법(ES6) (배열생성자 new Array() / Array.of() / Array.from() ) (0) | 2023.04.17 |
[javascript] 다차원 배열을 일차원으로 변경하는 방법 flat(), flatMap(), map() (1) | 2023.03.14 |