JavaScript 객체와 불변성

Jason Moon·2022년 5월 17일
0
post-thumbnail

기본형 데이터와 참조형 데이터

자바스크립트가 제공하는 타입은 크게 원시타입(primitive type)과 객체타입(object/reference type)으로 구분할 수 있다.

원시타입은

  • Boolean
  • null
  • undefinded
  • Number
  • String
  • Symbol

객체타입은

  • 원시타입 이외의 모든 값

원시타입과 객체타입을 구분하는 이유는 크게 세 가지 측면에서 다르기 때문이다.

  1. 원시 값은 변경 불가능한 값(immutable value)이고 객체(참조)타입의 값, 즉 객체는 변경 가능한 값(mutable value)이다.
  2. 원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값이 저장된다. 객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값이 저장된다.
  3. 원시 값을 갖는 변수를 다른 변수에 할당하면 원본의 원시 값이 복사되어 전달된다. 이를 값에 의한 전달이라한다. 반면 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달된다. 이를 참조에 의한 전달이라 한다.

불변객체를 만드는 방법

객체는 변경 가능한 값이므로 재할당 없이 직접 변경을 할 수 있다. 프로퍼티를 추가하거나 삭제할 수 있고, 프로퍼티 값을 갱신 할 수 있다.
그렇기 때문에 예상치 못한 곳에서 원치 않게 객체가 변경될 수 있다.
이를 방지하기 위한 몇 가지 방법들이 있다.

1. Object.assign

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된다.

얕은 복사(Shallow copy)와 깊은 복사(Deep 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 객체가 보기에 똑같아도 참조값이 다른 별개의 객체다.

Quiz

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다.

2.Object.freeze

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를 하여야 한다.

📕참고

profile
어려워 보여도 시간을 들여서 해보면 누구나 할 수 있는 일이다

0개의 댓글

관련 채용 정보