본문 바로가기
Tech/JS

불변성 Immutability

by egas 2021. 7. 30.

Immutability(변경불가성)는 객체가 생성된 이후 그 상태를 변경할 수 없는 디자인 패턴이다. 

 

객체는 참조 형태로 전달하고 전달 받는다. 의도하지 않은 객체의 변경이 발생하는 원인의 대다수는 “객체의 레퍼런스를 참조한 다른 객체에서 객체를 변경”하기 때문이다. 

 

var user = {
  name: 'hochan',
  gender: 'male'
}

var changeName = function (user, newName) {
    var newUser = user;
    newUser.name = newName;
    return newUser;
};

var user2 = changeName(user, 'holee');

console.log(user.name, user2.name) // holee holee
console.log(user === user2) // true

user와 newUser는 메모리에 저장된 같은 객체의 주소를 갖고 있다.

 

All primitives are immutable.
- https://developer.mozilla.org/en-US/docs/Glossary/Primitive

모든 기본 형들은 불변값이고, 기본 자료형 이외의 자료형은 모두 객체 자료형이며, 가변 값이다.

 

const, let과 Immutability의 차이점은 아래와 같다.

  • const, let: 재할당 및 재선언 불가능하다. 
  • Immutability: 메모리 영역에서의 변경이 불가능하다.

즉 const와 let의 경우 중첩된 객체 구조에서 하위 객체들에 대해서는 참조 형태이기 때문에 Immutability을 보장받지 못한다.

 

첫 번째 구문이 실행되면, 메모리의 문자열 'hello'를 생성하고, 식별자 str은 생성된 문자열 'Hello'의 메모리 주소를 가리킨다. 두번째 구문에서는 메모리에 문자열 'world'가 새로 생성되고, 식별자 str이 문자열 'world'의 메모리 주소를 가르킨다. 이후 메모리에 저장된 'Hello'에 대한 참조가 없으면, GC가 수거한다.

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

 

'Hello'가 저장된 메모리는 GC에 수거되기 전까지는 값이 변하지 않는다. 따라서, 우리는 기본형에 대해 불변성을 갖는다고 한다.

 

하지만, 객체의 경우 위에서 본 예시와 같이 불변성을 갖지 않는다.

 

아래 otherStr에는 

var str = "hello"
var otherStr = str

console.log(otherStr) // hello world

 

불변성을 보장받는 법

우리는 다음과 같은 방법으로 객체의 불변성을 보장받을 수 있다.

 

  • 새로운 객체를 생성
    • Object.assign (shallow copy)
    • Object literal
    • 하나하나 일일이 복사
    • Spread operator (shallow copy)
    • History API
    • Notification API
    • JSON.parse(JSON.stringify(obj))
      • parse로 새로운 문자열을 다시 만드는 게 단점
      • 순환 객체를 처리할 수 없다.
      • const x = {};
        const y = {x};
        x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
        const copy = JSON.parse(JSON.stringify(x)); // throws!
      • Maps, Sets, RegExps, Dates, ArrayBuffers 등은 직렬화시 손실된다.
  • 객체를 불변 객체로 만들기
    • Object.freeze

 

Object.assgin

Object.assign은 ES6에 추가된 메서드로  enumerable 한 속성들을 복사한다. 단, 프로토타입은 복사되지 않는다.

 

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target); // Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget); // Object { a: 1, b: 4, c: 5 }
console.log(source) // Object { b: 4, c: 5 }
console.log(target === returnedTarget) // true

 

Object.assign을 사용하여 기존 객체를 변경하지 않고 객체를 복사하여 사용할 수 있다.

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign({}, target, source);

console.log(target); // Object { a: 1, b: 2 }
console.log(returnedTarget); // Object { a: 1, b: 4, c: 5 }
console.log(source) // Object { b: 4, c: 5 }
console.log(target === returnedTarget) // false

 

하지만, Object.assign은 완전한 deep copy를 지원하지 않는다. Nested Object는 Shallow copy 된다.

 

var user = {
  name: 'hochan',
  address: {
  	city: 'Seoul'
  }
}

const user2 = Object.assign({}, user);
user2.address.city = 'Busan';

console.log(user.address.city); // "Busan"
console.log(user2.address.city); // "Busan"

 

Object.freeze

ES6에 추가된 메서드인 Object.freeze 또한 객체를 불변 객체로 만들 수 있다.

const obj = {
  prop: 42
};

Object.freeze(obj);
obj.prop = 33; // Throws an error in strict mode

console.log(obj.prop);// 42

 

하지만, Object.freeze도 Nested Object에 대해 변경이 가능하다.

var user = {
  name: 'hochan',
  address: {
  	city: 'Seoul'
  }
}

Object.freeze(user)
const user2 = Object.assign({}, user);
user2.address.city = 'Busan';

console.log(user.address.city); // "Busan"
console.log(user2.address.city); // "Busan"

 

Nested Object에 대해서는 하나하나 중첩 안의 객체들에 대해서 freeze로 처리해 주어야 한다. 

 

Object.assign과 Object.freeze을 사용하여 불변 객체를 만드는 방법은 번거로울뿐더러 성능상 이슈가 있어서 큰 객체에는 사용하지 않는 것이 좋다.

 

Immutable.js

우리는 해당 문제를 해결하기 위해 Facebook이 만든 immutable.js도 사용할 수 있다.

 

https://youtu.be/I7IdS-PbEgI

참고

https://dassur.ma/things/deep-copy/

 

Deep-copying in JavaScript — surma.dev

How do I copy an object in JavaScript? It’s a simple question, without a simple answer.

surma.dev

https://poiemaweb.com/js-immutability

 

Immutability | PoiemaWeb

함수형 프로그래밍의 핵심 원리이다. 객체는 참조(reference) 형태로 전달하고 전달 받는다. 객체가 참조를 통해 공유되어 있다면 그 상태가 언제든지 변경될 수 있기 때문에 문제가 될 가능성도

poiemaweb.com

 

728x90

'Tech > JS' 카테고리의 다른 글

.filter(Boolean)  (0) 2021.07.30
undefined 초기화  (0) 2021.07.30
ES5 vs ES6  (0) 2021.07.30
자바스크립트 꼬리물기와 for에 관하여...  (0) 2021.05.28

댓글