자바스크립트의 값과 객체: 변수에는 무엇이 들어 있는가
표현식이 만드는 값, 이름과 값의 연결, 원시 값과 객체의 차이를 차례로 익혀 공유된 객체의 변경과 함수 인수의 동작을 주소나 포인터 비유 없이 예측한다.
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
표현식은 실행하거나 계산하면 하나의 값을 내놓는 코드 조각이다. 그 결과를 얻는 과정을 평가라고 한다.
이 글의 핵심은 다음 네 문장이다.
- 표현식은 값으로 평가된다.
- 변수 선언은 이름과 값을 연결한다.
- 숫자·문자열과 객체는 모두 변수에 연결할 수 있다.
- 서로 다른 두 이름이 같은 객체 값에 연결되어 있으면, 한쪽을 통한 객체 변경이 다른 쪽에서도 보인다.
“변수 안에 주소가 들어 있다”거나 “객체는 참조로 전달된다”는 문장을 먼저 외울 필요는 없다. 자바스크립트 코드가 실제로 관찰할 수 있는 값, 이름, 객체 공유부터 구분하면 뒤의 결과를 예측할 수 있다.
읽기 전에 알아둘 코드 표기
이 글은 다음 문법만 사용한다.
const name = expression은expression을 평가한 결과에name이라는 이름을 연결한다.let은 나중에 그 이름을 다른 값에 다시 연결할 수 있는 선언이다.{ count: 1 }은count속성을 가진 객체를 만드는 표현식이다.profile.name은profile이라는 이름을 읽어 얻은 객체의name속성을 읽는다.- 이 글에서는
a === b를 두 이름을 읽었을 때 같은 객체를 얻는지 확인하는 용도로만 사용한다. 숫자와 특별한 값까지 포함한 정확한 규칙은 다음 글에서 설명한다. console.log(value)는 예제의 값을 개발자 도구 콘솔에 표시한다.function rename(profile) { ... }은rename이라는 함수를 선언하며,rename(user)는 함수를 호출한다.
ECMAScript는 자바스크립트 언어의 동작 규칙을 정한 표준이다. 자바스크립트 엔진은 그 규칙에 따라 코드를 실제로 실행하는 프로그램이다. 여기서 메모리는 프로그램 실행 중 데이터를 보관하는 공간이라는 일반적인 뜻으로만 사용한다. 특정 자바스크립트 엔진이 값을 몇 바이트로 저장하는지, 실제 주소가 무엇인지는 이 글의 설명 범위가 아니다.
값과 이름을 먼저 나눈다
다음 코드를 세 부분으로 나누어 보자.
const total = 1 + 21 + 2라는 표현식을 평가한다.- 그 결과로 숫자 값
3을 얻는다. total이라는 이름을 그 값과 연결한다.
스코프(scope)는 어떤 이름을 사용할 수 있는 코드의 범위다. 지금은 중괄호나 함수 안팎에서 같은 이름이 서로 다른 연결을 가질 수 있다는 정도만 알아두면 충분하다.
“값이 상자에 들어간다”는 그림도 간단한 숫자 예제에는 도움이 된다. 하지만 객체를 다루기 시작하면 상자 안에 객체가 통째로 복사되는지, 주소가 들어 있는지 같은 새로운 추측이 필요해진다. 이름과 값의 연결이라는 모델에는 그 추측이 필요 없다.
자바스크립트 값은 크게 두 무리로 나뉜다
입문 단계에서는 다음처럼 구분하면 된다.
| 무리 | 예 | 이 글에서 중요한 성질 |
| --- | --- | --- |
| 원시 값 | undefined, null, true, "안녕", 42 | 값 자체의 내부를 바꾸지 않는다 |
| 객체 값 | {}, [], 함수 | 속성을 가질 수 있고 각각의 객체는 동일성이 있다 |
그 밖에 Symbol은 서로 겹치지 않는 표식을 만들 때 쓰는 값이고, BigInt는 Number가 정확하게 표현할 수 있는 정수 범위보다 큰 정수를 다루는 값이다. 지금은 두 종류도 객체가 아닌 값이라는 분류만 기억하면 된다.
typeof null이 "object"를 반환하는 오래된 언어 동작 때문에 혼동할 수 있지만,
명세의 언어 타입 분류에서 null은 원시 값이다. typeof 연산자의 문자열 결과와
값의 분류를 같은 표로 외우지 않는다.
원시 값이 “단순해서 작다”, 객체가 “커서 복잡하다”는 구분도 정확한 기준은 아니다. 문자열은 길 수 있고 객체는 속성이 없을 수도 있다. 이 글에서 필요한 차이는 객체가 고유한 동일성을 가지며 속성 변경을 허용할 수 있다는 점이다.
원시 값을 대입한 뒤에는 각 이름을 따로 본다
먼저 숫자 예제를 한 줄씩 예측해 보자.
let first = 10
let second = first
first = 20
console.log(first) // 20
console.log(second) // 10let second = first를 실행할 때 first 표현식은 숫자 값 10으로 평가된다.
second는 그 값으로 초기화된다. 그 뒤 first = 20은 first라는 이름을 숫자 값
20에 다시 연결한다. second의 연결을 바꾸는 코드는 실행하지 않았으므로
second는 여전히 10이다.
문자열도 같은 관점으로 볼 수 있다.
let greeting = '안녕'
const copiedGreeting = greeting
greeting = greeting + '하세요'
console.log(greeting) // 안녕하세요
console.log(copiedGreeting) // 안녕greeting + '하세요'는 기존 문자열의 내부를 바꾸지 않고 새 문자열 값을 만든다.
그 새 값에 greeting을 다시 연결한다.
객체를 대입하면 같은 객체를 공유할 수 있다
객체 표현식이 몇 번 평가되는지 먼저 세어 보자.
const original = { count: 1 }
const alias = original
alias.count = 2
console.log(original.count) // 2
console.log(alias === original) // true{ count: 1 }은 한 번만 평가되었다. original은 그 객체 값에 연결되고,
alias = original을 평가할 때도 같은 객체 값을 얻는다.
상황을 이름과 값의 관계로만 그리면 다음과 같다.
original ─┐
├── 같은 객체 값 { count: 2 }
alias ────┘반면 다음 코드는 객체 표현식을 두 번 평가한다.
const first = { count: 1 }
const second = { count: 1 }
second.count = 2
console.log(first.count) // 1
console.log(second.count) // 2
console.log(first === second) // false두 객체는 속성 모양이 처음에 같았지만 서로 따로 만들어졌다. 객체가 “같다”는 말의
정확한 비교 규칙은 다음 글에서 ===와 Object.is를 통해 다룬다.
재대입과 객체 변경은 다른 동작이다
비슷해 보이는 두 줄을 나란히 놓아 보자.
let profile = { name: '민지' }
profile.name = '서준' // 객체의 속성을 변경
profile = { name: '하린' } // 이름을 다른 객체 값에 재대입첫 번째 줄은 profile의 연결을 그대로 둔 채 기존 객체의 name 속성을 바꾼다.
두 번째 줄은 새 객체를 만들고 profile을 그 값에 다시 연결한다.
다른 이름이 이전 객체를 공유하고 있으면 차이가 더 분명해진다.
let profile = { name: '민지' }
const oldProfile = profile
profile.name = '서준'
console.log(oldProfile.name) // 서준
profile = { name: '하린' }
console.log(profile.name) // 하린
console.log(oldProfile.name) // 서준속성을 바꿀 때는 공유된 하나의 객체가 달라졌다. 재대입 뒤에 profile을 읽으면 새
객체를 얻고, oldProfile을 읽으면 이전 객체를 얻는다.
const가 막는 것은 이름의 재대입이다
const settings = { theme: 'dark' }
settings.theme = 'light' // 가능
settings = { theme: 'light' } // TypeErrorTypeError는 허용되지 않은 재대입 때문에 실행 중 발생하는 오류의 종류다.
두 번째 대입은 settings의 연결을 바꾸려 하기 때문에 실패한다. 첫 번째 대입은
settings를 읽어 얻은 기존 객체의 속성을 바꾸므로 const 바인딩 규칙과 충돌하지
않는다.
따라서 다음 두 문장은 구분해야 한다.
const로 선언한 이름은 다른 값으로 재대입할 수 없다.- 그 이름을 읽어 얻는 객체는 별도 조치가 없다면 변경될 수 있다.
const가 객체를 불변으로 만드는 것은 아니다. 복사와 Object.freeze가 무엇을
보장하는지는 시리즈의 세 번째 글에서 따로 다룬다.
함수에는 인수의 값이 전달된다
함수 호출에서도 이름과 값을 분리하면 “값 전달인가, 참조 전달인가”라는 말싸움 없이 결과를 예측할 수 있다.
const profile = { name: '민지' }
function replace(localProfile) {
localProfile = { name: '서준' }
}
replace(profile)
console.log(profile.name) // 민지호출 시 profile 표현식이 평가한 객체 값으로 함수의 매개변수 localProfile이
초기화된다. 함수 안에서 localProfile에 새 객체를 대입해도 함수 바깥의 profile
바인딩을 재대입한 것은 아니다.
이번에는 속성을 바꿔 보자.
const profile = { name: '민지' }
function rename(localProfile) {
localProfile.name = '서준'
}
rename(profile)
console.log(profile.name) // 서준함수 호출 직후 profile과 localProfile은 같은 객체 값에 연결되어 있다. 이번
코드는 매개변수를 재대입하지 않고 그 객체의 속성을 바꿨으므로 함수 바깥에서도 결과가
보인다.
React에서도 props와 state는 자바스크립트 값이다
React의 컴포넌트는 입력을 받아 화면 조각을 계산하는 함수나 클래스다. 부모 컴포넌트가 자식 컴포넌트에 전달하는 입력을 props라고 한다.
props와 state에 객체가 들어가도 그 객체가 새로운 자바스크립트 종류로 바뀌지는 않는다. 다른 이름에 대입하면 앞에서 본 객체 공유 규칙이 그대로 적용된다. 다음 코드는 React 함수를 생략하고 객체 관계만 떼어 낸 예제다.
const profileFromState = { name: '민지' }
const alias = profileFromState
alias.name = '서준'
console.log(profileFromState.name) // 서준alias를 만들 때 새 객체를 만드는 표현식은 실행되지 않았다. 따라서
profileFromState와 alias는 같은 객체를 읽는다.
이 연결을 알면 React에서 객체 상태를 직접 바꿨을 때 왜 다른 코드에서도 변경이 보이는지 설명할 수 있다. React가 이전 상태와 다음 상태를 어떻게 구분하고 새 객체를 어떻게 만드는지는 두 번째와 세 번째 글에서 이어서 다룬다.
코드를 읽을 때 묻는 네 가지 질문
변수와 객체가 섞인 코드를 만나면 다음 순서로 표시해 본다.
- 어떤 표현식이 평가되는가? 객체 리터럴처럼 새 객체를 만드는 표현식이 몇 번인지 센다.
- 각 이름은 어느 값에 연결되는가? 대입문을 실행한 시점의 값을 따라간다.
- 재대입인가, 객체 변경인가?
name = ...과name.property = ...을 구분한다. - 같은 객체를 공유하는 다른 이름이 있는가? 있다면 객체 변경은 그 이름을 통해서도 관찰된다.
이 네 질문으로 첫 번째 글의 모든 예제를 설명할 수 있다. 다음 글에서는 마지막
질문을 코드로 판별하는 ===, Object.is, 같은 객체인지의 판단을 살펴본다.
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01`const total = 1 + 2`에서 표현식의 값과 바인딩의 이름은 각각 무엇일까?
정답
`1 + 2`는 숫자 값 3으로 평가되고, `total`은 그 값과 연결되는 바인딩의 이름이다.
관련 설명 다시 읽기왜 그런가
표현식의 평가 결과와 그 결과를 나중에 사용하기 위한 이름을 구분하면 변수의 역할을 정확히 설명할 수 있다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02`let first = 10; let second = first; first = 20` 뒤의 `second`는 얼마일까?
정답
`second`는 10이다. `first = 20`은 `first`를 다른 숫자 값에 다시 연결할 뿐 `second`를 바꾸지 않는다.
관련 설명 다시 읽기왜 그런가
숫자는 원시 값이며 두 이름이 이후에 함께 움직이는 연결이 만들어진 것이 아니다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03`const alias = original` 뒤에 `alias.count = 2`를 실행하면 `original.count`는 얼마일까?
정답
`original.count`도 2다. 두 이름이 같은 객체 값에 연결되어 있으므로 한쪽을 통한 객체 변경을 다른 쪽에서도 관찰한다.
관련 설명 다시 읽기왜 그런가
바깥 이름은 둘이지만 새 객체를 만든 표현식은 한 번뿐이다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04함수 안에서 객체 매개변수에 새 객체를 대입하면 호출한 쪽의 변수도 새 객체로 바뀔까?
정답
아니다. 함수 안의 매개변수 바인딩만 새 객체 값으로 다시 연결된다.
관련 설명 다시 읽기왜 그런가
다만 매개변수와 호출한 쪽 이름이 처음에 같은 객체 값에 연결되어 있다면, 그 객체의 속성을 변경한 결과는 양쪽에서 보인다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
05React 상태에서 읽은 객체를 `const alias = profile`로 다른 이름에 연결하면 상태 객체가 복사될까?
정답
아니다. `alias`와 `profile`은 같은 객체 값에 연결되므로 `alias`를 통한 속성 변경은 React가 이전 렌더링에서 읽은 객체도 변경할 수 있다.
관련 설명 다시 읽기왜 그런가
React의 props와 state도 자바스크립트 값이므로 객체 대입과 공유 규칙은 그대로 적용된다. React에서는 이 값을 렌더링별 스냅샷으로 다루므로 직접 변경하지 않는다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- ECMAScript Language TypesEcma International · 표준 명세 · 확인 2026-07-27
- The Object TypeEcma International · 표준 명세 · 확인 2026-07-27
- Object InitializerEcma International · 표준 명세 · 확인 2026-07-27
- SameValueNonNumberEcma International · 표준 명세 · 확인 2026-07-27
- Environment RecordsEcma International · 표준 명세 · 확인 2026-07-27
- Let and Const DeclarationsEcma International · 표준 명세 · 확인 2026-07-27
- FunctionsMDN Web Docs · 공식 문서 · 확인 2026-07-27
- Components and Hooks must be pureReact · 공식 문서 · 확인 2026-07-27
- Updating Objects in StateReact · 공식 문서 · 확인 2026-07-27