자바스크립트가 제공하는 타입은 크게 원시타입(primitive type)과 객체타입(object/reference type)으로 구분할 수 있다.
원시타입은
객체타입은
원시타입과 객체타입을 구분하는 이유는 크게 세 가지 측면에서 다르기 때문이다.
객체는 변경 가능한 값이므로 재할당 없이 직접 변경을 할 수 있다. 프로퍼티를 추가하거나 삭제할 수 있고, 프로퍼티 값을 갱신 할 수 있다.
그렇기 때문에 예상치 못한 곳에서 원치 않게 객체가 변경될 수 있다.
이를 방지하기 위한 몇 가지 방법들이 있다.
Object.assign은 타겟 객체로 소스 객체의 프로퍼티를 복사한다.
Object.assign(target, ...sources)
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
console.log(obj == copy); // false
const o4 = { a: 1 };
const o5 = { b: 2 };
const o6 = { c: 3 };
const merge2 = Object.assign({}, o4, o5, o6);
console.log(merge2); // { a: 1, b: 2, c: 3 }
console.log(o4); // { a: 1 }
Object.assign을 사용해 기존 객체를 변경하지 않고 객체를 복사할 수 있다. Object assign은 완전한 deep copy를 지원하지는 않는다. 객체 내부의 객체(Nested Object)는 Shallow copy된다.
객체를 프로퍼티 값으로 갖는 객체의 경우 얇은 복사는 한 단계까지만 복사하는 것을 말하고 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 말한다.
참고) 원시 값을 할당한 변수를 다른 변수에 할당하는 것을 깊은 복사, 객체를 할당한 변수를 다른 변수에 할당하는 것을 얕은 복사라고 부르는 경우도 있다.
const a = { x: { y: 1 } };
// 참조 값을 복사
const a1 = a; // { x: { y: 1 } };
console.log(a1 === a); // true
console.log(a1.x === a1.x); // true
// 얕은 복사
const b = { ...a }; // { x: { y: 1 } };
console.log(b === a); // false 얕은 복사가 돼서 b는 a의 { x : OO} 똑같은 모습이지만 다른 객체이다.
console.log(b.x === a.x); // true 하지만 {y:1} 부분은 a와 같은 참조값을 가지고 오기 때문에 true
// lodash의 cloneDeep을 사용한 깊은 복사
const _ = require('lodash');
// 깊은 복사
const c = _.cloneDeep(a); //{ x: { y: 1 } };
console.log(c === a); // false
console.log(c.x === a.x); // false
// 중첩된 부분까지 모두 복사해서 원시 값처럼 완전한 복사본을 가진다.
// a 원본과 c 객체가 보기에 똑같아도 참조값이 다른 별개의 객체다.
let fruit1 = {
name: '🍎',
};
let fruit2 = {
name: '🍎',
};
console.log(fruit1 === fruit2); // 1번
console.log(fruit1.name === fruit2.name); // 2번
1번과 2번의 결과는??
객체 리터럴은 평가될 때마다 객체를 생성한다. 비록 fruit1, fruit2가 가리키는 객체의 내용은 같지만 다른 메모리에 저장된 각각 다른 객체이다. 따라서 1번은 false
하지만 프로퍼티 값을 참조하는 fruit1.name과 fruit2.name은 값으로 평가될 수 있는 표현식이다. 두 표현식 모두 원시 값 '🍎'으로 평가된다. 따라서 2번은 true다.
Object.freeze()를 사용하면 불변(immutable)객체로 만들 수 있다.
const user1 = {
name: 'Lee',
address: {
city: 'Seoul'
}
};
// Object.assign은 완전한 deep copy를 지원하지 않는다.
const user2 = Object.assign({}, user1, {name: 'Kim'});
console.log(user1.name); // Lee
console.log(user2.name); // Kim
Object.freeze(user1);
user1.name = 'Kim'; // 무시된다!
console.log(user1); // { name: 'Lee', address: { city: 'Seoul' } }
console.log(Object.isFrozen(user1)); // true
하지만 객체 내부의 객체(Nested Object)는 변경가능하다.
내부 객체까지 변경 불가능하게 만들려면 Deep freeze를 하여야 한다.