개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

값과 참조 — 변수의 정체와 복사 전략

2025년 8월 19일
JavaScript메모리원시값참조값

a = 1과 a = { name: 'Kim' }은 겉으로는 똑같은 할당처럼 보입니다. 그런데 복사하거나 비교하거나 함수에 넘기는 순간 전혀 다르게 동작합니다. JavaScript가 두 종류의 데이터를 메모리에 완전히 다른 방식으로 저장하기 때문입니다.

원시값과 참조값의 종류

JavaScript의 모든 데이터 타입은 원시값과 참조값 두 범주로 나뉩니다.

원시값(Primitive)

  • number, string, boolean
  • null, undefined
  • symbol, bigint

참조값(Reference)

  • object (일반 객체, 배열, 함수 모두 포함)

메모리 구조 — 스택과 힙

원시값은 스택에 값 자체가 저장되고, 참조값은 힙에 실제 데이터가 저장되며 스택에는 그 주소만 담깁니다.

[ stack ]                   [ heap ]
┌─────────────┐
│  a = 42     │  ← value
├─────────────┤
│  b = "hi"   │  ← value
├─────────────┤             ┌──────────────────┐
│  c = 0x1A3F │ ──addr──▶  │ { name: 'Kim' }  │
└─────────────┘             └──────────────────┘

스택은 크기가 고정된 데이터를 빠르게 읽고 쓰는 데 적합합니다. 객체처럼 크기가 가변적인 데이터는 힙에 두고, 스택에는 그 위치(주소)만 저장합니다.

스택(Stack): 함수 호출 시 생성되는 고정 크기 메모리 영역. 함수가 끝나면 자동으로 해제됩니다.

힙(Heap): 동적으로 크기가 결정되는 데이터를 저장하는 메모리 영역. 가비지 컬렉터가 관리합니다.

복사 동작의 차이

원시값 — 값 자체가 복사됩니다

let a = 10;
let b = a;  // 10이라는 값이 복사됨

b = 99;

console.log(a); // 10 — a는 영향 없음
console.log(b); // 99

b를 바꿔도 a는 그대로입니다. 서로 독립된 복사본이기 때문입니다.

참조값 — 주소가 복사됩니다

const user1 = { name: 'Kim' };
const user2 = user1;  // 주소가 복사됨

user2.name = 'Lee';

console.log(user1.name); // 'Lee' — user1도 바뀜!
console.log(user2.name); // 'Lee'

user1과 user2는 같은 객체를 가리키는 두 개의 주소입니다.

[ stack ]                   [ heap ]
┌──────────────┐
│ user1 = 0x1A │ ──┐        ┌──────────────────┐
├──────────────┤   └──▶     │ { name: 'Lee' }  │
│ user2 = 0x1A │ ──────▶    └──────────────────┘
└──────────────┘

두 변수가 동일한 힙 주소를 공유하므로, 하나를 통해 내부를 수정하면 나머지에도 반영됩니다.

비교(===)의 차이

원시값 — 값으로 비교합니다

console.log(1 === 1);         // true
console.log('hello' === 'hello'); // true

참조값 — 주소로 비교합니다

const a = { x: 1 };
const b = { x: 1 };
const c = a;

console.log(a === b); // false — 내용이 같아도 주소가 다름
console.log(a === c); // true  — 같은 주소를 가리킴

내용이 동일한 두 객체라도 서로 다른 힙 위치에 저장되면 ===는 false를 반환합니다.

함수에 넘길 때의 차이

// 원시값: 함수 내부의 변경이 외부에 영향 없음
function addTen(n) {
  n += 10;
}

let score = 50;
addTen(score);
console.log(score); // 50 — 변하지 않음


// 참조값: 함수 내부의 변경이 외부에 반영됨
function rename(obj) {
  obj.name = 'Park';
}

const person = { name: 'Kim' };
rename(person);
console.log(person.name); // 'Park' — 바뀜

함수에 참조값을 넘기면 주소가 전달되므로, 함수 내부에서 프로퍼티를 수정하면 원본 객체가 변경됩니다.

독립 복사본 만들기

얕은 복사 — 중첩 없는 객체에 적합합니다

const original = { name: 'Kim', age: 30 };

// spread 연산자
const copy1 = { ...original };

// Object.assign
const copy2 = Object.assign({}, original);

copy1.name = 'Lee';
console.log(original.name); // 'Kim' — 독립적

중첩 객체가 있으면 내부 객체는 여전히 주소를 공유합니다.

const a = { info: { city: 'Seoul' } };
const b = { ...a };

b.info.city = 'Busan';
console.log(a.info.city); // 'Busan' — 중첩 객체는 공유됨

깊은 복사 — 중첩 구조도 완전히 분리합니다

const a = { info: { city: 'Seoul' } };
const b = structuredClone(a);

b.info.city = 'Busan';
console.log(a.info.city); // 'Seoul' — 완전히 독립

structuredClone: 브라우저와 Node.js 17 이상에서 지원하는 내장 깊은 복사 함수입니다. Date, Map, Set 등도 올바르게 복사합니다.

요약

구분원시값참조값
저장 방식스택에 값 자체 저장스택에 주소, 힙에 데이터 저장
복사값이 독립적으로 복사됨주소가 복사되어 같은 객체 공유
비교(===)값이 같으면 true같은 주소일 때만 true
종류number, string, boolean,
null, undefined,
symbol, bigint
object, array, function
상황방법
중첩 없는 객체얕은 복사 — { ...obj }, Object.assign
중첩 있는 객체깊은 복사 — structuredClone(obj)

변수에 담기는 것이 값인지 주소인지를 파악하면, 예상치 못한 데이터 변경과 비교 오류의 원인을 빠르게 찾을 수 있습니다. 참조값을 독립적으로 복사할 때는 중첩 구조 여부에 따라 방법을 구분합니다.

다음 글 →스코프 — 변수가 살아있는 범위