원시 타입(primitive type)의 값, 즉 원시 값은 변경 불가능한 값(immutable value)이다. 다시 말해, 한번 생성된 원시 값은 읽기 전용 값으로서 변경할 수 없다.
여기서 변경 불가능하다는 것은 변수가 아니라 값에 대한 진술이다. 즉, "원시 값은 변경 불가능하다"는 말은 원시 값 자체를 변경할 수 없다는 것이지 변수 값을 변경할 수 없다는 것이 아니다. 변수는 언제든지 재할당을 통해 변수 값을 변경할 수 있다.
❓ 변수와 상수의 차이점은 무엇일까?
👉 변수의 상대 개념인 상수는 재할당이 금지된 변수를 말한다. 상수도 값을 저장하기 위한 메모리 공간이 필요하므로 변수라고 할 수 있다. 단, 변수는 언제든지 재할당을 통해 변수 값을 변경할 수 있지만 상수는 단 한번만 할당이 허용되므로 변수 값을 변경할 수 없다.
✔ 원시 값은 어떤 일이 있어도 불변한다. 이러한 원시 값의 특성은 데이터의 신뢰성을 보장한다. 불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법이 없다.
원시 타입별로 메모리 공간의 크기가 미리 정해져 있다. 단, ECMAScript 사양에 문자열 타입(1개의 문자당 2byte)과 숫자 타입(8byte) 이외의 원시 타입은 크기를 명확히 규정하고 있지는 않아서 브라우저 제조사의 구현에 따라 원시 타입의 크기는 다를 수 있다.
✔ 자바스크립트의 문자열은 원시 타입이며, 변경 불가능 하다. 이것은 문자열이 생성된 이후에는 변경할 수 없음을 의미한다.
❓ 문자열의 한 문자를 변경할 수 있나 ?
👉불가능하다. 문자열은 변경 불가능 한 값이기 때문이다. 하지만 문자열은 유사 배열 객체이면서 이터러블이므로 배열과 유사하게 각 문자에 접근할 수 있다.
var str = 'string';
str[0] = 'S';
console.log(str); // string로 변경이 되지 않는다.
유사 배열 객체란 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말한다. 문자열은 마치 배열처럼 인덱스를 통해 각 문자에 접근할 수 있으며, length 프로퍼티를 갖기 때문에 유사 배열 객체이고 for 문으로 순회할 수도 있다.
var str = 'string';
console.log(str[0]); // s
console.log(str.length); // 6
변수에 원시 값을 갖는 변수를 할당하면 할당받는 변수에는 할당되는 변수의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달이라 한다.
var a = 80;
var copy = a;
console.log(a, copy); // 80 80
console.log(a === copy); // true
a = 100;
console.log(a, copy); // 100 80
console.log(a === copy); // false
❗ a변수와 copy변수의 값 80은 다른 메모리 공간에 저장된 별개의 값이다.
✔ 결국은 두 변수의 원시 값은 서로 다른 메모리 공간에 저장된 별개의 값이 되어 어느 한쪽에서 재할당을 통해 값을 변경하더라도 서로 간섭할 수 없다
객체(참조) 타입의 값, 즉 객체는 변경 가능한 값(mutable value)이다. 원시 값을 할당한 변수가 기억하는 메모리 주소를 통해 메모리 공간에 접근하면 원시 값에 접근할 수 있다. 객체를 할당한 변수가 기억하는 메모리주소를 통해 메모리 공간에 접근하면 참조 값(reference value)에 접근할 수 있다. 참조 값은 생성된 객체가 저장된 메모리 공간의 주소, 그 자체다.
✨ 원시 값은 변경 불가능한 값이므로 재할당 외에는 원시 값을 갖는 변수의 값을 변경할 방법이 없다. 하지만 객체는 변경 가능한 값으로 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다.
객체를 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계까지만 복사하는 것을 말하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 말한다. 얕은 복사와 깊은 복사로 생성된 객체는 원본과는 다른 객체다. 즉, 원본과 복사본은 참조 값이 다른 별개의 객체다.
const obj = {a : { c : 1 }, b : { d : 2 }};
const copy = {...obj};
obj === copy; // false
obj.a === copy.a; // true
copy.a.c = 3;
console.log(copy.a); // { c : 3 }
console.log(obj.a); // { c : 3 }
obj.a === copy.a; // true
❓ 한 단계 더 깊이 있는 depth인 경우도 다른 참조 값을 바라보게 하고싶을땐?
👉 얕은 복사 대신 깊은 복사를 사용한다.
const obj = {a : { c : 1 }, b : { d : 2 }};
const _ = require('lodash');
const copy = _.cloneDeep(obj);
obj === copy; // false
obj.a === copy.a; // false
copy.a.c = 3;
console.log(copy.a); // { c : 3 }
console.log(obj.a); // { c : 1 }
obj.a === copy.a; // true
✔ 한 단계 더 깊이 있는 depth인 .a는 바뀌지 않는 것을 확인 할 수 있다.
객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달이라 한다. 이것은 두 개의 식별자가 하나의 객체를 공유한다는 것을 의미한다. 따라서 어느 한쪽에서 객체를 변경하면 서로 영향을 주고받는다.
var person = {
name: 'Cho';
};
// 얕은 복사 : 참조 값을 복사
var copy = person;
var person1 = {
name: 'Cho';
};
var person2 = {
name: 'Cho';
};
console.log(person1 === person2); // false
console.log(person1.name === person2.name); // true
✔ person1과 person2는 다른 메모리에 저장된 별개의 객체이기 때문에 참조값 또한 다르다. 따라서 첫번째 console문은 false이다.
✔ 프로퍼티 값을 참조하는 person~.name은 값으로 평가될 수 있는 표현식이다. 두 표현식 모두 원시 값 'Cho'로 평가되기 때문에 두번째 console문은 true이다.