모던 자바스크립트 (7)

Daon·2023년 3월 29일
0

모던자바스크립트

목록 보기
7/12
post-thumbnail

객체와 변경불가성(Immutability)

Immutability(변경불가성)는 객체가 생선된 이후 그 상태를 변경할 수 없는 디자인 패턴을 의미합니다.
Immutability은 함수형 프로그래밍의 핵심 원리입니다.

객체는 참조 형태로 전달하고 전달 받습니다. 객체가 참조를 통해 다른 변수로 공유되어 있다면 그 변수를 통해 원본 값이 바뀔 가능성이 있기 때문에
이를 해결하기 위해 참조가 아닌 객체의 방어적 복사를 통해 새로운 객체를 생성한 후 변경합니다.
또는 Observer 패턴으로 객체의 변경에 대처가 가능합니다.

1. immutabel value vs. mutable value

javascript의 원시타입은 변경 불가능한 값이다.
원시타입 이외의 모든 값은 객체타입이며 객체 타입은 변경이 가능한 값이다.

아래의 코드를 살펴보면

var str = 'Hello';
str = 'world';

처음 구문에서 메모리에 문자열 'Hello'가 생성되고 식별자 str은 메모리에 생성된 문자열 'Hello'의 주소를 가르킨다.
두번째 구문이 실행되면 이전에 생성된 문자열 'Hello'를 수정하는 것이 아니라
새로운 문자열 'world'를 생성하고 식별자 str은 그것을 가르킨다.
문자열 'Hello'와 'world'는 모두 메모리에 존재하는 것이다.

var statement = 'I am an immutable value'; // string은 immutable value

var otherStr = statement.slice(8, 17);

console.log(otherStr);   // 'immutable'
console.log(statement);  // 'I am an immutable value'

위에 예제는 slice함수를 통해서 statement 변수에 저장된 문자열을 변경하는 것처럼 보이지만
slice 함순느 statement 새롭게 만들고 그 문자열을 slice 하여 문자열을 반환하는 것이다.

2. 불변 데이터 패턴(immutable data pattern)

의도하지 않게 데이터가 변경이 된다면 대다수의 이유는 "레퍼런스를 참조한 다른 객체에서 객체를 변경"하기 때문이다

아래의 예제처럼말이다.

var user1 = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

var user2 = user1; // 변수 user2는 객체 타입이다.

user2.name = 'Kim';

console.log(user1.name); // Kim
console.log(user2.name); // Kim

위의 문제를 방지하기 위해 객체의 변경이 필요한 경우에는 참조가 아니라
객체의 방어적 복사를 통해 새로운 객체를 생성하는 것이 좋다

  • 객체의 방어적 복사 : Object.assign
  • 불변객체화를 통한 객체 변경 방지 : Object.freeze

2-1 Object.assign

타깃 객체로 소스 객체의 프로퍼티를 복사한다.
이때 동일한 프로퍼티 같은 경우에는 덮어쓰기가된다.

// Syntax
Object.assign(target, ...sources)
// Copy
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
console.log(obj == copy); // false

// Merge
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const merge1 = Object.assign(o1, o2, o3);

console.log(merge1); // { a: 1, b: 2, c: 3 }
console.log(o1);     // { a: 1, b: 2, c: 3 }, 타겟 객체가 변경된다!

// Merge
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 }

그러나 위와 같은경우는 다차원 배열,객체를 생각하지않은 얕은 복사이기 때문에 만약
다차원 배열,객체를 복사해야 할 경우에는 위 방법을 제외한 방법으로 해야한다

2-2 Object.freeze

위 메서드를 사용하면 불변객체로 만들 수 있다.

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

freeze 또한 assign처럼 얕은 속성의 메서드 이기 때문에 객체 내부 user1.address.city = 'busan'처럼 깊은 변경은 막을 수 없다.

이를 해결하기 위해선 따로 함수를 생성하여 deepFreeze 해야한다

function deepFreeze(obj) {
  const props = Object.getOwnPropertyNames(obj);

  props.forEach((name) => {
    const prop = obj[name];
    if(typeof prop === 'object' && prop !== null) {
      deepFreeze(prop);
    }
  });
  return Object.freeze(obj);
}

const user = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

deepFreeze(user);

user.name = 'Kim';           // 무시된다
user.address.city = 'Busan'; // 무시된다

console.log(user); // { name: 'Lee', address: { city: 'Seoul' } }

2-3 Immutable.js

위에 2가지 방법은 번거로울 뿐더러 성능상 이슈가 있어서 큰객체에는 사용하지 않는 것이 좋다.
위에 방법들을 대안으로 Immutable.js 라이브러리를 사용하는 방법이있다.
위 라이브러리는 List, Stack, Map, OrderedMap, Set 등 영구 불변 데이터 구조를 제공한다.

const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50

map1.set(‘b’, 50)의 실행에도 불구하고 map1은 불변하였다. map1.set()은 결과를 반영한 새로운 객체를 반환한다.

profile
같이 일하고싶은 그런 개발자!

0개의 댓글