기본형 자료형 & 참조형 자료형

const testObj = {
  age: 28,
  name: `doodream`,
};

testObj2 = testObj;
testObj2.age = 100;

console.log(testObj.age); // 100
console.log(testObj2.age); // 100

위 코드에서 testObj2에 testObj를 복사해 넣었으니 같은 형의 틀이 복사되어 넣어지겠지라고 생각되겠지만 그렇지 않다. 분명히 testObj2의 age만 100으로 변경했을 뿐인데 testObj age도 100으로 변경되었다. 이러한 이유를 알려면 기본형 자료형과 참조형 자료형이 자바스크립트 엔진에서 어떻게 동작하는지 알아야한다.

위 그림에서 기본형 자료형은 자바스크립트엔진에 실행컨텍스트에 쌓이게 되어 CallStack에 존재한다. 반면 객체(모든 참조형 객체들)들은 힙에 저장되어진다.

기본형의 데이터 저장방식

let age = 30;
let oldAge = age;
age = 31;
console.log(age)// 30;
console.log(oldAge)// 31;

위 코드에서 처음에 age변수는 30이라는 값을 가르키는 주소값을 가르킨다. oldAge라는 값에 age에 넣을때 age가 가르키는 주소값을 넣는 것이므로 oldAge도 0001 이라는 주소값을 가르킨다. (특정된 메모리의 주소값은 변경할 수 없다. 즉, 이미 30이라는 값을 가진 메모리의 주소값을 바꿀수 없다는 말이다)

그런데 age에 31이라는 새로운 값을 재 할당했다.
이때 31은 새로운 값이므로 새로운 주소를 할당해서 31 이라는 값을 할당했고 이를 age는 새로운 주소값을 가르키게 된다.

따라서 우리가 헷갈리는 상황이 만들어지지 않는다. age와 oldAge는 아얘 다른 값을 가지고 있으며 다른 주소를 갖기 때문이다.

참조형 데이터 저장방식

const me = {
  name: 'Jonas',
  age: 30
};

const friend = me;
friend.age = 27;

console.log('Friend:', friend);
// { name: 'Jonas', age: 27 }
console.log('Me:', me);
// { name: 'Jonas', age: 27 }

me 객체가 생성되면 힙에 저장된다. 이 me 식별자가 가르키는 것은 힙에 대한 메모리 주소가 아니다. me 가 가르키는 값은 스텍에 저장되는 힙에 저장된 값의 주소값이다.

다시한번 정리하자면 me 식별자는 스텍에 저장된 힙 주소값을 값으로 가지는 스텍 주소를 가르키고, 힙 주소값은 힙에서 객체값을 가지는 힙 메모리 주소이다.

  • 객체의 값은 스텍에 저장되기에 너무 거대하다. 따라서 대신 힙에 저장된다. 힙은 거의 무제한의 메모리 풀이라고 생각하자. 스텍에는 객체가 실제로 힙에 저장되는 위치를 참조한다. 필요할 때마다 찾을수 있도록 말이다.

  • friend라는 식별자에 me 를 대입하여 객체를 넣는다고 생각해보자. 이때에는 me에는 객체의 힙주소를 가르키는 값을 가지는 스텍의 주소를 담고 있으므로 스텍주소를 friend에 넣게되고 friend는 me와 같은 스텍주소를 가르키게된다. 따라서 friend는 본질적으로 me 객체와 똑같다.

  • friend에서 age값을 조작한다면 me 객체를 조작하는 것과 다름이 없다. 왜냐면 friend는 스텍의 값을 바꾼적이 없기 때문이다. 그렇다면 friend에 다른 객체로 인식하게 하려면 스텍의 값이 다르게 객체를 넣어주면 된다.

💡 또한 const 값은 이러한 스텍의 값(원시값)을 바꾸지 못하게 할뿐 힙주소에 저장되어 있는 값까지 바꿀수 있게 하지 않는다.
위코드에서 friend에 전혀다른 값을 재할당 한다면 constant error가 발생할 것이다. 즉, const, let은 힙에 저장된 값과는 아무 상관이없다.

즉, 위코드에서 우리는 객체를 복사한다고 생각할 때마다 그냥 같은 객체를 가르키는 새로운 식별자를 생성할 뿐이다.

새로운 객체 복사

Object.assign()

const testObj = {
  age: 28,
  name: `doodream`,
  info: ['seoul', 'macbook'],
};

testObj2 = Object.assign({}, testObj);
testObj2.name = `DDo`;
console.log(testObj.name); // doodream
console.log(testObj2.name);// DDo

testObj2.info.push('good man');

console.log(testObj.info);// ["seoul", "macbook", "good man"] 
console.log(testObj2.info);// ["seoul", "macbook", "good man"] same!!

Object.assign() 이라는 함수는 객체두개를 병합시켜준다. 따라서 빈객체와 기존객체를 합치면 힙에 새로운 객체를 형성시켜준다. 따라서 다른 힙주소를 가르키고 있으므로 새로운 객체의 속성의 값을 변화시켜주면 기존객체는 변화가 없는 것을 확인 할수 있다.

하지만 이경우 새로운 객체의 속성값이 기본형 자료형일때에 해당되는 이야기이다. 참조형 자료형의 경우 콜스텍에서 힙주소를 가르키는 값은 바뀌지 않았고 재할당하지 않는한 여전히 기존객체의 참조형 자료형과 같은 힙주소를 가르킨다. 따라서 위 예제를 살펴보자

testObj.info는 참조형 자료형으로 힙에 실제 배열값을 가지고 있는 힙주소를 가르킨다. 이 힙주소값은 Object.assign에서 그대로 복사가되어 testObj2.info에 그대로 적용되었다. 따라서 testObj2.info === testObj.info 이며 값은 힙주소 값을 가지고 같은 힙안에 있는 배열을 가르킨다. 따라서 한쪽을 수정하면 다른쪽도 같은 배열을 가르키기 때문에 깊은 복사가 이루어 지지 않는것이다.

💡 해결 방법으로서는 JSON, Lodash 라이브러리 등 다른 방법들이 있고 차후 포스팅 하겠습니다.

profile
일상을 기록하는 삶을 사는 개발자 ✒️ #front_end 💻

0개의 댓글