1강. 원시 값과 객체 — JavaScript의 메모리 모델

by gg582 · 2026-07-09 13:26:26 · 43 views

1강. 원시 값과 객체 — JavaScript의 메모리 모델


들어가기 전에

JavaScript를 배우면서 가장 많이 접하는 버그는 다음과 같습니다.

const a = { value: 10 };
const b = a;

b.value = 20;

console.log(a.value);

많은 초보자는 다음과 같이 예상합니다.

10

그러나 실제 출력은

20

입니다.

왜 그럴까요?

C에서는 구조체를 대입하면 복사가 일어나고,

Point a{1, 2};
Point b = a;

Python에서는 객체 참조가 복사됩니다.

a = [1,2]
b = a

JavaScript 역시 Python과 비슷하지만, 내부 동작은 조금 더 체계적으로 이해할 필요가 있습니다.

이번 강의에서는 JavaScript의 메모리 모델을 통해 이러한 동작을 설명합니다.


JavaScript의 데이터 타입

ECMAScript는 데이터를 크게 두 종류로 나눕니다.

  • Primitive Value (원시 값)
  • Object (객체)

원시 값은 더 이상 분해할 수 없는 값입니다.

대표적인 원시 타입은 다음과 같습니다.

  • Number
  • BigInt
  • String
  • Boolean
  • undefined
  • null
  • Symbol

반면 객체는 여러 값을 하나로 묶는 복합 자료형입니다.

대표적으로

  • Object
  • Array
  • Function
  • Date
  • Map
  • Set

등이 모두 객체입니다.


Primitive는 Immutable이다

원시 값은 Immutable(불변) 입니다.

예를 들어

let x = 10;

x = 20;

많은 입문자는

"10이 20으로 바뀌었다."

고 생각합니다.

실제로는 그렇지 않습니다.

개념적으로는

x

↓

10

에서

x

↓

20

으로 새로운 값을 다시 바인딩한 것입니다.

숫자 10 자체가 수정된 것이 아닙니다.

문자열도 마찬가지입니다.

let str = "Hello";

str += " World";

문자열 내부가 수정되는 것이 아니라

새로운 문자열

"Hello World"

가 생성되고,

str이 그것을 다시 참조하게 됩니다.


Object는 Mutable이다

객체는 다릅니다.

객체는 내부 상태를 변경할 수 있습니다.

const user = {
    age: 20
};

user.age = 30;

여기서

const

는 변수 자체를 다시 대입하지 못하게 할 뿐,

객체 내부는 얼마든지 수정할 수 있습니다.

따라서

user.age = 30;

은 정상적으로 동작합니다.

많은 초보자가

const

를 "불변 객체"라고 오해하지만,

실제로는

불변인 것은 변수의 바인딩이며 객체 자체가 아닙니다.


변수에는 객체가 저장되지 않는다

JavaScript에서 가장 중요한 개념입니다.

다음 코드를 보겠습니다.

const person = {
    name: "Alice"
};

많은 사람은

person

↓

{name:"Alice"}

라고 생각합니다.

실제로는 개념적으로 다음과 같습니다.

Call Stack

person
 │
 ▼

Heap

{
    name: "Alice"
}

객체는 Heap에 저장됩니다.

변수는 Heap에 있는 객체를 직접 저장하는 것이 아니라,

그 객체를 식별할 수 있는 참조(Reference)를 저장합니다.

JavaScript는 이 참조를 직접 노출하지 않습니다.

C의 포인터처럼 주소 연산을 수행할 수도 없습니다.

그러나 개념적으로는

"객체를 가리키는 참조"

라고 이해하면 충분합니다.


참조의 복사

다음 예제를 보겠습니다.

const a = {
    score: 100
};

const b = a;

메모리 구조는 다음과 같습니다.

Call Stack

a ───────┐
          │
b ───────┘
          │
          ▼

Heap

{
    score: 100
}

새로운 객체가 생성된 것이 아닙니다.

참조만 하나 더 복사되었습니다.

따라서

b.score = 0;

을 수행하면

Heap의 동일한 객체가 수정됩니다.

결과적으로

console.log(a.score);

0

을 출력합니다.


값 전달과 참조 전달

JavaScript는 모든 인자를 값으로 전달(Call by Value) 합니다.

그러나 객체에서는 혼동이 발생합니다.

다음 코드를 보겠습니다.

function change(x) {
    x = 100;
}

let value = 10;

change(value);

console.log(value);

출력은

10

입니다.

원시 값은 복사되어 전달됩니다.

반면 객체는

function change(user) {

    user.age = 30;

}

const user = {
    age: 20
};

change(user);

console.log(user.age);

의 결과가

30

입니다.

왜일까요?

함수는 참조 자체를 복사받습니다.

즉,

user

↓

Reference

↓

Object

에서

Reference만 하나 더 복사됩니다.

따라서 함수 내부와 외부가 같은 객체를 바라보게 됩니다.

이를 흔히

"참조 전달"

이라고 부르지만,

엄밀히 말하면 JavaScript는 참조를 값으로 복사(Call by Sharing) 하는 방식입니다.


얕은 복사(Shallow Copy)

객체를 복사하고 싶다면 어떻게 해야 할까요?

많은 사람이

const b = a;

를 사용합니다.

하지만 이것은 복사가 아닙니다.

참조를 하나 더 만든 것뿐입니다.

객체를 새로 만들려면

const b = {
    ...a
};

또는

const b = Object.assign({}, a);

를 사용할 수 있습니다.

이를 얕은 복사라고 합니다.

최상위 객체만 새로 생성됩니다.


얕은 복사의 함정

다음 예제를 보겠습니다.

const user = {

    profile: {

        age: 20

    }

};

const copy = {

    ...user

};

copy.profile.age = 30;

많은 사람은

user.profile.age

↓

20

을 기대합니다.

그러나 실제 결과는

30

입니다.

메모리는 다음과 같습니다.

Call Stack

user ───────┐

copy ──────┘

      │

      ▼

profile Object

겉 객체는 새로 만들어졌지만

내부 객체는 여전히 공유됩니다.

이것이 얕은 복사의 한계입니다.


깊은 복사(Deep Copy)

모든 중첩 객체까지 복사하려면

깊은 복사가 필요합니다.

최근 JavaScript에서는

const clone = structuredClone(user);

를 사용할 수 있습니다.

이 함수는

객체

배열

Map

Set

Date

등 대부분의 내장 객체를 새로운 객체로 복사합니다.

복사 후에는

clone.profile.age = 100;

을 수행해도

원본은 변경되지 않습니다.


Object.freeze()

JavaScript에서는 객체를 수정하지 못하게 만들 수도 있습니다.

const user = {

    age: 20

};

Object.freeze(user);

이후

user.age = 30;

은 변경되지 않습니다.

그러나

const user = {

    profile: {

        age: 20

    }

};

Object.freeze(user);

에서는

user.profile.age = 30;

이 가능합니다.

freeze() 역시 얕게 적용되기 때문입니다.

중첩 객체까지 모두 고정하려면 별도의 Deep Freeze 구현이 필요합니다.


JavaScript에서 흔히 발생하는 버그

다음 코드는 실무에서도 자주 발견됩니다.

const config = {

    options: {

        darkMode: false

    }

};

const backup = {

    ...config

};

backup.options.darkMode = true;

개발자는

설정을 복사했다고 생각합니다.

그러나

console.log(config.options.darkMode);

true

를 출력합니다.

원인은 얕은 복사입니다.

객체를 복사했다고 생각했지만,

실제로는 내부 객체를 공유하고 있었던 것입니다.

이러한 버그는 React, Vue, Node.js 등 거의 모든 JavaScript 프로젝트에서 반복적으로 등장합니다.


이번 강의 요약

JavaScript의 데이터는 원시 값과 객체로 구분됩니다.

원시 값은 불변(Immutable)이며, 값을 변경하는 것이 아니라 새로운 값을 다시 바인딩합니다.

객체는 가변(Mutable)이며 Heap에 저장됩니다.

변수에는 객체 자체가 아니라 객체를 식별하는 참조가 저장됩니다.

객체를 대입하면 객체가 복사되는 것이 아니라 참조가 복사됩니다.

객체를 복사하려면 얕은 복사 또는 깊은 복사를 명시적으로 수행해야 합니다.

이 차이를 이해하지 못하면 JavaScript에서 가장 흔한 공유 상태(Shared State) 버그를 피하기 어렵습니다.


다음 강의

다음 강의에서는 JavaScript 함수의 실행 구조를 살펴봅니다.

  • 함수도 객체인 이유
  • 일급 객체(First-class Function)
  • 클로저(Closure)
  • 렉시컬 스코프와 실행 컨텍스트의 관계
  • 클로저가 메모리에 남는 이유
  • 실무에서 클로저를 사용하는 대표적인 패턴

함수의 내부 구조를 이해하면 JavaScript가 왜 함수형 프로그래밍을 적극적으로 지원하는 언어인지 자연스럽게 이해할 수 있습니다.

Back

Comments

No comments yet.