티스토리 뷰

JavaScript

JS - 불변성(ps. Immutable)

김관장 2022. 2. 24. 17:17

1.불변성?

객체는 참조 형태로 값을 주고 받는데,  하나의 객체가 생성되고 그 값을 다른 객체들이 참조 하고 있다면 의도 하지 않은 값의 변형으로 문제가 발생하는 경우가 있을 수 있다. 보통 이런 경우 레퍼런스를 참조한 다른 객체에서 객체를 변경 하기 때문이다. 이와 같은 문제를 방지 하기 위해서는 불변의(immutable) 객체를 만들거나 객체 복사를 통해 새로운 객체를 생성 한 후 변경하는 것이 안전하다.

let a = 10;
let b = a;
a = 20;
console.log(a,b); //20 10

위 코드는 변수 a에 10을 할당하고, 변수 b는 a가 가리키는 주소를 가리킨다. 이때 a를 20으로 변경하면 a와 b모두 20이 아닌 a만 20으로 바뀌게된다.  

이유는 Boolean, Numbe, String, null, undefined, Symbol 과 같은 타입은 기본적으로 불변성을 유지하기 때문이다.

이와 반대로 Object 타입들은 변경가능한 값이다.

 

2.객체 타입의 불변성을 지키려면..

(2-1) 스프레드 문법 사용 

// 스프레드 문법을 사용하여 객체를 복사해야지 객체가 불변성을 유지 할 수 있다.
let info = { name:'a', age:28 };
let new_info = {...info};
info.name = 'b';
console.log(info.name, new_info.name); //b a

// 하지만 1레벨 깊이에서만 유효하게 동작하기 때문에 객체 내부의 객체의 불변성까지는 유지할수 없다.
let info = { name:'a', fake:{age:28} };
let new_info = {...info};
info.fake.age = 30;
console.log(info.fake.age, new_info.fake.age); //30 30

=> 만약 2레벨 이상의 깊이에서 불변성을 유지해주려면
   별도의 변수에 래벨별 값을 재할당하고 다시 복사한 객체의 값에 넣어주는 작업을 반복해야한다.

(2-2) Object 메서드 사용

// Object.assign() 메서드는 타깃 객체의 속성만을 대상으로 객체로 복사 한다.
// 하지만 1레벨 깊이에서만 유효하게 동작하기 때문에 객체 내부의 객체의 불변성까지는 유지할수 없다.
let o1 = { a: 1 };
let o2 = { b: 2 };
let obj1 = Object.assing({},o1,o2);
console.log(obj1, o1); // {a:1,b:2} {a:1}

let o3 = { a:1 };
let o4 = { b:2 };
let o5 = { c:3 };
let obj2 = Object.assign(o3,o4,o5);
console.log(obj2, o3); // {a:1,b:2,c:3} {a:1,b:2,c:3}
// => 이처럼 타겟 객체 자체가 변경됨
// Object.freeze() 메서드를 사용하면 더 이상 수정할 수 없는 불변성의 객체를 만들 수 있다.
let o1 = { a: 1 };
let obj1 = Object.freeze(o1);
o1.a = 10; //재할당 불가
console.log(o1); //Error

// 하지만 Object.assign 와 마찬가지로 객체 내부의 객체에는 대응 하지 못한다. 
// => 이경우 deepFreeze 해야한다.
const user = { 
  name: 'Jack', 
  body: { 
    weight: 100 
  } 
}
Object.freeze(user)
user.body.weight = 200 // 재할당 가능하다.
console.log(user) // { user: 'Jack', body: { weight: 200 } }

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: 'Jack', 
  body: { 
    weight: 100 
  } 
}
deepFreeze(user)
user.name = 'Jack' // 재할당 불가
user.body.weight = 200 // 재할당 불가
console.log(user) // 오류

Object.assign과 Object.freeze을 사용하여 완전한 불변 객체를 만드는 방법은 추가 비용이 들기도 하고 성능상 이슈가 있어서 큰 객체에는 사용하지 않는 것이 좋다.

(2-3) Immutable 라이브러리 사용

Immutable js에서 Map은 객체 대신 사용하는 데이터 구조이다. (자바스크립트의 내장함수 Map과는 다르다.)

 

const { Map, fromJS } = require('immutable');

//ImmutableJS에서 Map은 객체 대신 사용하는 데이터구조이다
const m1 = Map({ a: 1, b: 2, });
const m2 = Map({ a: 1, b: Map({ c: 'hello', d: 10, }), e: 11, });

//m2와 같이 내부에 일일이 그 내부까지 Map으로 만들기 힘들떄 fromJS를 사용한다.
//fromJS를 사용하면 내부 배열은 List로 만들고, 내부 객체는 Map으로 만든다.
const m3 = fromJS({ a: 1, b: { c: 'hello', d: 10, }, e: 11, });

//Immutable 객체를 일반 객체 형태로 변형
const m4 = m2.toJS();

'JavaScript' 카테고리의 다른 글

JS - JSX?  (0) 2022.03.31
JS - Map() 과 Set()  (0) 2022.03.02
JS - 함수 (일반, 리터럴-익명)  (3) 2022.02.21
JS - 배열 메소드 (forEach, some, every, map, reduce, filter)  (4) 2022.02.21
JS - 동기, 비동기  (0) 2022.02.19
댓글