Lighthouse of FE biginner

[JavaScript] 원시 값과 객체 리터럴 본문

자바스크립트

[JavaScript] 원시 값과 객체 리터럴

[FE] Lighthouse 2024. 11. 16. 14:11
모던 자바스크립트 Deep Dive 스터디 3회차

자바스크립트 스터디를 진행하며 해당 회차에 공부했던 내용을 직접 그림을 그리고 코드를 실행하며 저의 글로 작성합니다.

 

객체

자바스크립트에서는 원시 값을 제외한 모든 값이 객체이다. 배열, 함수, 정규식 등 모든 것들이 객체이다.

 

원시 값은 변경 불가능하다. 메모리에 한번 선언이 되고 원시 값의 값을 변경한다면 해당 주소의 값이 변경 되는 것이 아닌 새로운 공간에 메모리를 할당하고 해당 주소에 값을 매핑한다. 반면에 객체는 메모리에 선언된 후 해당 주소의 값을 변경할 수 있다. 이 말은 새로운 객체를 변수에 할당하는 것이 아닌 해당 객체의 프로퍼티 값을 변경해도 메모리의 주소가 달라지지 않는다는 것이다.

 

프로퍼티

객체는 프로퍼티의 집합이고 프로퍼티는 문자열 또는 심벌 타입의 키와 값으로 구성된다. 값은 원시 값이나 어떠한 객체가 들어갈 수 있다.

프로퍼티의 키는 가능하면 네이밍 컨벤션을 지켜야 한다. 네이밍 컨벤션을 지키지 않아도 키로 선언될 수 있지만 그 경우에는 문자열임을 알리는 따옴표를 사용해야 하고, 해당 프로퍼티에 접근하기 위해서 대괄호 표기법을 사용해야 한다.
객체에 존재하지 않는 프로퍼티에 접근하면 undefined를 반환한다. Dynamic한 객체를 사용할 땐 해당 프로퍼티가 undefined의 가능성을 내포하고 있다는 것에 주의해서 프로그래밍 해야한다.

 

프로퍼티의 제거

자바스크립트에서는 delete 연산자를 사용해 객체의 프로퍼티를 제거할 수 있습니다.

delete 연산자를 사용하면 간단하게 프로퍼티를 제거할 수 있지만, 특정 프로퍼티를 제거한 객체가 필요하다면 불변성 유지를 위해 원본 객체를 변경하는 것이 아닌 새로운 객체를 생성하는 것을 권장합니다.

 

객체 불변성을 유지하는 것은 다음과 같은 이점을 누릴 수 있습니다.

  • 원본 객체를 변경하지 않으므로 사이드 이펙트가 없습니다.
  • Immutable 패턴은 데이터 변경 추적, 디버깅, 상태 관리에서 유리합니다(예: React, Redux 등에서 자주 사용).

하지만 기존 객체가 메모리에 유지가 되기 때문에 메모리 누수가 발생할 수 있다는 점도 기억해야 합니다. 전역 변수와 같이 프로세스가 동작할 때 상시 유지되는 변수라면 불변성을 이용한 프로그래밍은 메모리 누수가 발생합니다.

Mutable Programming

// delete 연산자를 통해 해당 객체의 프로퍼티를 제거합니다. Mutable 합니다.
const obj = {
  a: 1,
  b: 2,
  c: {},
};

delete obj.c;

Immutable Programming

// 불변성을 위해 새로운 객체를 생성해 리턴합니다.
function deleteProperty(obj, propertyKey) {
  const keys = Object.keys(obj);
  return keys.reduce(
    (prev, cur) => ({
      ...prev,
      ...(cur === propertyKey ? {} : { [cur]: obj[cur] }),
    }),
    {}
  );
}

const obj = {
  a: 1,
  b: 2,
  c: {},
};

console.log("immutable", deleteProperty(obj, "c"));

 

 

원시 값과 객체의 비교

자바스크립트에서 원시 값은 변경 불가능(Immutable) 합니다. 즉 메모리에 값이 할당된다면 해당 메모리에는 새로운 값을 할당할 수 없습니다. 그리고 원시 값은 메모리에 실제 값이 저장이 됩니다.

원시 값에 값을 재할당 할 경우 새로운 메모리 공간을 할당 받습니다.

 

반면에 객체는 변경 가능(Mutable)합니다. 또한 메모리에 공간을 확보하면 해당 공간에 값을 직접 저장하는 것이 아닌 참조 값이 저장됩니다.

자바스크립트에서 객체는 변경 가능하다고 했습니다. 이는 메모리의 효율성을 위함 입니다. 만약 커다란 객체가 변경될 때 마다 메모리에 새로운 공간을 할당 받는다면 해당 객체를 복사하고 새로운 메모리에 할당하기까지의 수행 시간과 메모리 공간의 낭비가 정비례로 발생합니다.

이 특성에 의해 원시 값이 할당된 변수를 다른 변수에 할당한다면 값 전달 (call by value)에 의해 값이 복사되어 전달됩니다. 반면에 객체를 다른 변수에 할당한다면 주소 값 전달 (call by address)에 의해 참조 값이 복사 됩니다. (얕은 복사)

 

아래의 코드를 살펴봅시다.

 

let a = 1;
let b = a;

console.log(a, b); // 1, 1
a = 2;

console.log(a, b); // 2, 1

 

원시 값을 a 변수에 할당하고 b 변수에 a를 전달합니다. 이때 a의 값은 원시 값이기 때문에 call by value 값 전달으로 인해 실제 값이 전달됩니다.

 

아래 그림을 살펴보면 a 변수와 b 변수는 메모리 상 다른 공간을 바라보고 있습니다. 단순히 값이 전달 된 것 입니다.

a 변수에 2를 할당하면 메모리에는 다음과 같은 일이 발생합니다. 원시 값은 불변이기 때문에 메모리에 새로운 공간을 할당 받습니다.

const obj = { a: 1 };
const obj2 = obj;

console.log(obj, obj2); // { a: 1 } { a: 1 }

obj.a = 2;

console.log(obj, obj2); // { a: 2 } { a: 2 }

 

반면에 객체는 변경 가능합니다. obj에 객체를 할당하고 obj2에 obj 값을 할당합니다. 객체는 메모리에 참조 값을 저장하고 있습니다. 이로 인해서 obj2에는 obj의 참조 값이 저장이 됩니다.

 

4행의 콘솔의 결과는 { a: 1 } { a: 1 } 입니다.

 

6행에서 obj.a 의 값을 2로 변경합니다.

 

call by address 이기 때문에 8행 콘솔의 출력 결과는 { a: 2 } { a: 2 } 입니다.

 

메모리 위에서의 객체는 위와 같은 구조입니다. 같은 객체를 공유하고 있기 때문에 obj2에서 값을 변경할 경우 obj에서도 값이 변경이 됩니다. 메모리에 선언된 객체는 변경 가능하다는 특성으로 인해 obj에서는 원치 않는 사이드 이펙트가 발생할 수 있습니다. 그렇기 때문에 이를 정확하게 이해하고 코딩해야 합니다.

 

얕은 복사와 깊은 복사

프로그래밍에는 얕은 복사와 깊은 복사라는 개념이 존재합니. 얕은 복사는 주소에 의한 복사이고, 깊은 복사는 값에 의한 복사입니다. 즉 메모리에서 같은 공간을 바라보느냐, 별개의 공간을 할당받아서 바라보느냐 의 차이이입니다.