자바스크립트가 제공하는 7가지 데이터 타입(숫자, 문자열, 불리언, null, undefined, 심벌, 객체 타입)은 크게 원시 타입(primitive type)과 객체 타입(object/regerece type)으로 구분할 수 있다.
- 원시 타입 값 : 변경 불가
- 객체 타입 값 : 변경 가능
원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값이 복사되어 전달된 후 저장된다.
객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값이 복사되어 전달된 후 저장된다.
변수의 값을 원시 값으로 할당했을 때, 원시 값은 변경 불가능한 값이기 때문에 값을 직접 변경할 수 없다.
따라서 변수 값을 변경하기 위해 원시 값을 재할당하면 새로운 메모리 공간을 확보하고 재할당한 값을 저장한 뒤, 변수가 참조하던 메모리 공간의 주소를 변경한다. 값의 이러한 특성을 불변성(ummutability)이라 한다.
var score; // 변수 선언, undefined 라는 값을 메모리주소1에 저장
var score = 80; // 값의 할당, 메모리주소1에 undefined라는 값은 그대로 두고 메모리주소2에 80을 저장하여 참조하도록 한다
score = 90; // 값의 재할당, 메모리주소2에 80이라는 값은 그대로 두고, 메모리주소3에 90을 저장하여 참조하도록 한다
자바스크립트는 개발자의 편의를 위해 원시 타입인 문자열 타입을 제공한다. 자바스크립트의 문자열은 원시 타입이며, 변경 불가능하다. 이것이 문자열이 생성된 이후에는 변경할 수 없음을 의미한다.
var str = 'string';
// 문자열은 유사 배열 객체이면서 이터러블 이므로 배열과 유사하게
// 인덱스를 사용해 각 문자에 접근할 수 있다(원시 값과 래퍼 객체 참고)
// 문자열은 원시 값이므로 변경할 수 없지만 이때 에러가 발생하지 않는다
str[0] = 'S';
console.log(str); // string
// console.log(str[0]) -> 's' 는 되지만(접근 가능)
// str[0] = 'S' 로 변경은 안됨(재할당 불가, 문자열 = 원시 값)
var score = 80;
var copy = score;
console.log(score); // 80
console.log(copy); // 80
console.log(score === copy); // true
score = 100;
console.log(score); // 100
console.log(copy); // 80
console.log(score === copy); // false
// 두 변수의 값이 80으로 같지만 각각 다른 메모리 공간에 저장된 별개의 값이다
// 그러므로 score 에 100을 재할당하였을 때 copy의 값은 변하지 않는다
엄격하게 표현하면 변수에는 값이 전달되는 것이 아니라 메모리 주소가 전달된다. 이는 변수와 같은 식별자는 값이 아니라 메모리 주소를 기억한다고 생각하면 된다.
객체 타입의 값, 즉 객체는 변경 가능한 값이다.
원시 값을 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 원시 값에 접근할 수 있다.
즉, 원시 값을 할당한 변수는 원시 값 자체를 값으로 갖는다. 하지만 객체를 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 참조 값(reference value)에 접근할 수 있다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체다.
var person = {
name: 'Lee'
};
일반적으로 원시 값을 가지는 변수는 변수선언(undefined, 메모리주소1), 값의 할당(value, 메모리주소2) 단계를 거치며 메모리주소2에 할당한 값으로 초기화하지만, 객체의 경우 메모리주소2에는 메모리주소3(객체 그 자체)을 참조(reference)하라는 명령이 입력돼 있다고 생각하면 된다.
(원시 값을 갖는 변수: 2단계, 객체 값을 갖는 변수: 3단계)
객체의 이러한 특성 때문에, 객체를 할당한 변수는 재할당 없이 객체의 프로퍼티를 동적으로 추가할 수도 있고 프로퍼티 값을 갱신할 수도 있으며 프로퍼티 자체를 삭제할 수 있는 변경 가능한 값이 되는 것이다.
// 변경 가능 예시
var person = {
name: 'Lee'
};
// 프로퍼티 값 갱신
person.name = 'Kim';
// 프로퍼티 동적 생성
person.address = 'Seoul';
console.log(person); // {name: "Kim", address: "Seoul"}
원시 값은 변경 불가능한 값이므로 원시 값을 갖는 변수의 값을 변경하려면 재할당을 통해 메모리에 원시 값을 새롭게 생성해야 한다. 하지만 객체는 변경 가능한 값이므로 메모리에 저장된 객체를 직접 수정할 수 있다. 이때 객체를 할당한 변수에 재할당을 하지 않았으므로 객체를 할당한 변수의 참조 값은 변경되지 않는다. 객체는 이러한 구조적 단점에 따른 부작용이 있다. 그것은 원시 값과는 다르게 여러 개의 식별자가 하나의 객체를 공유할 수 있다는 것이다.
- 여러 개의 식별자가 하나의 객체를 공유할 때
같은 참조 값(객체 그 자체)를 가지므로 원본과 사본 중 어느 한쪽에서 객체를 변경(재할당이 아닌 프로퍼티 값을 변경하거나, 추가 및 삭제 등)하면 서로 영향을 주고 받는다)
var person = {
name: 'Lee'
};
// person 과 copy 는 동일한 객체를 참조한다
var copy = person;
console.log(copy === person); // true
copy.name = 'Kim';
person.address = 'Seoul';
// 동일한 객체를 참조하므로 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받는다
console.log(person); // {name: "Kim", address: "Seoul"}
console.log(copy); // {name: "Kim", address: "Seoul"}
var person1 = {
name: 'Lee'
};
var person2 = {
name: 'Lee'
};
console.log(person1 === person2); // 1. ??
console.log(person1.name ===.person2.name); // 2. ??
- false
객체 리터럴은 평가될 때마다 객체를 생성한다. 따라서 person1 변수와 person2 변수가 가리키는 객체는 내용은 같지만 다른 메모리에 저장된 별개의 객체다.- true
하지만 프로퍼티 값을 참조하는 person1.name 과 person2.name은 값으로 평가될 수 있는 표현식이다. 두 표현식 모두 원시 값 'Lee'로 평가된다.