JS는 7가지의 데이터 타입을 제공한다(숫자, 문자열, 불리언, null, undefined, 심벌, 객체 타입) 이는 크게 원시 타입, 객체 타입으로 나눌 수 있다. 이는 크게 3가지 측면에서 다르다
한번 생성된 원시값은 읽기 전용의 값으로써 값을 변경할 수 없다.
정확하게 말하면 변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 메모리 공간의 식별하기 위한 이름이고, 값은 변수에 저장된 데이터로 변경 불가능한 것은 변수가 아니라 값이다.
우리가 사용하는 변수 값을 바꾸는 것은 값이 변하는게 아니라 재할당을 통해 변수가 가리키는 값이 변하는 것이다.
변수의 상대 개념인 상수는 재할당이 금지된 변수이다.
이러한 불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법이 없다.
다른 언어와 다르게 JS는 문자열 타입이 있다. 이 문자열 타입도 원시값으로써 변경이 불가능하다
따라서 새로운 값을 할당하는 것도 원시값이 바뀌는 것이 아니라 재할당되는 것이다.
JS의 문자열은 유사배열 객체이며 이터러블(Symbol.itreator를 프로퍼티 키로 갖는 메서드를 구현하거나 상속받은 객체로 순회 할 수 있는 특성이 있음)이므로 배열과 유사하게 문자에 접근할 수 있다.
하지만 원시값인 문자열을 변경할 수는 없다.
var str = 'string';
// 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다.
console.log(str[0]); // s
// 원시 값인 문자열이 객체처럼 동작한다.
console.log(str.length); // 6
console.log(str.toUpperCase()); // STRING
// 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다.
// 하지만 문자열은 원시값이므로 변경할 수 없다. 이때 에러가 발생하지 않는다.
str[0] = 'S';
console.log(str); // string
3번째 차이점인 복사 같은 경우도 원시값은 복사를 한 후 다른 메모리 공간에 저장된 별개의 값을 갖는다.
var score = 80;
var copy = score;
console.log(score); // 80
console.log(copy); // 80
score = 100;
console.log(score); // 100
console.log(copy); // 80
위 그림처럼 동일한 값을 갖는 새로운 값을 메모리에 저장하고 변수는 새로운 값을 가리키기 때문에 두 값은 연결되어있지 않아 영향을 주지 않는다. (깊은 복사)
객체 타입의 값은 변경이 가능한 값이다. 객체를 변수에 할당하면 실제 값이 아닌 참조 값(실제 값이 저장된 메모리 주소값)이 저장되며 객체를 할당한 변수를 참조하면 메모리에 저장되어 있는 참조 값을 통해 실제 객체에 접근한다
객체는 변경 가능한 값으로 메모리에 저장된 객체를 직접 수정한다. 이때 객체를 할당한 변수를 재할당 하는 것이 아니라 할당도니 변수의 실제 값이 바뀌는 것이므로 참조 값이 바뀌는 것은 아니다.
⇒ 위의 이유로 array, object를 const에 할당하고 값은 변경할 수 있는 것이다.
3번째 차이점인 복사 같은 경우 객체는 실제 값을 복사하는게 아니라 실제값의 참조값을 복사하기 때문에 하나의 실제 객체 값을 여러개의 식별자가 공유할 수 있고 서로에게 영향을 주는 경우가 생긴다. (참조에 의한 전달)
var person = {
name: 'Lee'
};
// 참조값을 복사(얕은 복사). copy와 person은 동일한 참조값을 갖는다.
var copy = person;
// copy와 person은 동일한 객체를 참조한다.
console.log(copy === person); // true
// copy를 통해 객체를 변경한다.
copy.name = 'Kim';
// person을 통해 객체를 변경한다.
person.address = 'Seoul';
// copy와 person은 동일한 객체를 가리킨다.
// 따라서 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받는다.
console.log(person); // {name: "Kim", address: "Seoul"}
console.log(copy); // {name: "Kim", address: "Seoul"}
객체의 프로퍼티 값으로 객체를 갖는 경우는 스프레드 문법을 사용해도 한 단계만 복사가 된다. 즉, 새로운 참조값을 할당받아도 그 안에있는 프로퍼티에 저장된 값은 참조 값이므로 완전히 다른 값이 아니라 연결되어있는값이다.
이를 피하기 위해서 객체 값을 모두 복사하는 깊은 복사 방법이 있는데 가장 많이 쓰이는 라이브러리가 lodash 의 cloneDeep 메서드를 사용한다.
const o = { x: { y: 1 } };
// 얕은 복사
const c1 = { ...o }; // 35장 "스프레드 문법" 참고
console.log(c1 === o); // false
console.log(c1.x === o.x); // true
// lodash의 cloneDeep을 사용한 깊은 복사
// "npm install lodash"로 lodash를 설치한 후, Node.js 환경에서 실행
const _ = require('lodash');
// 깊은 복사
const c2 = _.cloneDeep(o);
console.log(c2 === o); // false
console.log(c2.x === o.x); // false