Immutability, 불변성

이짜젠·2021년 7월 12일
0
post-custom-banner

데이터의 원본이 훼손되지 않도록(불변하게) 개발하는 방법

Immutability는 불변(성), 불역성(不易性)(unchangeableness)이라는 뜻을 갖고있는 단어다.
Javascript에서 Immutability란 데이터의 원본이 훼손되지 않도록(불변하게) 개발하는 방법을 의미한다.

"데이터의 원본이 훼손된다?" 어떤의미인지 코드로 알아보자.

var a = 'haha'
a = 'hoho'

var a = 'haha'는 내부적으로 아래와 같이 동작한다.

  1. a 라는 변수를 선언
  2. 메모리공간을 할당, undefined로 초기화
  3. 'haha'라는 값을 할당

그렇다면 다음줄인 a = 'hoho' 코드는 어떤식으로 동작을 하게 될까?

A. 'haha'가 저장되어 있는 메모리공간의 값을 수정(재할당) ?

B. 새로운 메모리 공간을 할당하여 'hoho'를 저장하고, a변수와 재연결 ?

A 처럼 기존의 메모리영역의 값을 수정하는 경우를 "Mutable 하다"고 표현한다.
이는 원본데이터를 훼손시킬 수 있다는 의미이기도하다.

B처럼 기존 값의 변경이 불가능하여 매번 새로운 메모리공간을 확보하여 값을 할당하는 경우를 "Immutable 하다" 고 한다.

javascript에서 문자열은 immutable data로 취급되기 때문에, a = 'hoho'는 B로 동작하게 된다. 다시말해, Immutability은 아래와 같이 정리할 수 있다.

변수의 값을 변경할 때, 기존의 값을 수정하지 않고 새로운 값으로 재할당하도록 개발하는 방식


Immutable data

원본 데이터의 수정이 불가능한 데이터

기존 값의 변경이 불가능하기때문에, 수정 시 매번 메모리를 새로 할당받는 데이터다.

대표적으로 원시타입(primitive type)의 데이터들은 Immutable data 로 취급된다.
위에서 예시로 들었던 a = 'hoho' 같은 문자열 데이터타입도 immutable하게 동작한다.

  • Boolean
  • null
  • undefined
  • Number
  • String
  • Symbol

한가지 주의해야할 타입은 String 다.

문자열은 일반적으로 문자들의 배열형태로 저장하게 된다.
C는 문자열의 인덱스에 접근하여 문자열안의 특정 문자를 수정할 수 있지만, javascript에서의 문자열은 immutable 하기때문에 불가능하다.

var str = 'hello';

console.log(str[0], str[1]); // h, e

str[0] = 'h'; // 무시됨
str[1] = 'i'; // 무시됨

// 변함이 없다.
console.log(str[0], str[1]); // h, e
console.log(str); // hello

Immutable한 데이터는 복사할 때 새로운 메모리영역에 값이 저장되었다.
복사본과 원본이 독립적인 데이터이기에, 원본데이터의 변경이 복사본에 영향을 미치지 않는다..


Mutable data

원본 데이터의 수정이 가능한 데이터

mutable 한 데이터로, 메모리영역에 있는 기존의 값을 변경할 수 있다.

Javascript에서 Object타입의 데이터들은 모두 Mutable data 으로, 원시타입을 제외한 모든 값들은 Mutable data로 볼 수 있다.

  • Object
  • Array
    ....
// mutable data
var arr = [];
var obj = { name: 'chun', age: 30 };
var arr2 = arr;
var obj2 = obj;

console.log(arr2, obj2); // [], { name: 'chun', age: 30 }

arr.push(1);
obj.name = 'ginameee'
obj.age = 29;

console.log(arr2, obj2); // [ 1 ], { name: 'ginameee', age: 29 }

obj2, arr2의 값을 변경한적이 없지만, 변경되어있다.
Mutable한 데이터는 복사본과 원본이 같은 메모리영역을 참조하고있기 때문에, 원본의 데이터 값이 변경되면 복사본의 데이터도 변경된다.


Immutability 패턴

위와 같은 사태를 방지하기위한, Mutable data를 Immutable 하게 개발하기위해선 크게 2가지 방법으로 구현이 가능하다.

  • Matable data를 수정할 때, 메모리를 새로 할당받는다.
    (Immutable data처럼 동작하게 만들기)
  • Mutable data의 수정을 막아버린다.

각각의 방법을 알아보자.

새로운 영역을 할당

Mutable data를 변경 할 때, Immutable data처럼 새로운 메모리에 변경된 값을 할당하고, 변수에 새로 할당한다.

ES6에서 제공하는 Object.assign메소드나, Destructuring 문법을 이용하여 구현이 가능하다.

Object.assign

Object.assign(target, ...sources)은 ES6에 추가된 메소드로, sources 객체들의 프로퍼티들을 target 객체로 복사하는 기능을 한다. (공통된 프로퍼티는 덮어씌워진다.)
target에 빈객체를 넣어준다면, sources 객체(들)를 Immutable하게 복사하는것과 동일한 효과를 볼 수 있고, 이를 이용해서 Immutability 적용이 가능하다.

var arr = [1, 2, 3];
var copiedArr = arr;
var assignedArr = Object.assign([], arr);

arr.push(4);

console.log(arr); // [1, 2, 3, 4]
console.log(copiedArr); // [1, 2, 3, 4] 
console.log(assignedArr); // [1, 2, 3]
var obj = {};
var copiedObj = obj;
var assignedObj = Object.assign({}, obj);

obj.name = 'chun';

console.log(obj); // {name: "chun"}
console.log(copiedObj); // {name: "chun"}
console.log(assignedObj); // {}

비구조화 문법 (Destructuring)

구조화된 배열 또는 객체를 비구조화(분해)하여 개별적인 변수에 할당하는 ES6에서 제공하는 문법이다.
비구조화하는 과정에서, 비구조화된 데이터를 새로운메모리 영역에 할당하는데, 마찬가지로 이를 이용해서 Immutability를 적용할 수 있다.

var arr = [1, 2, 3];
var copiedArr = arr;
var destructuredArr = [...arr];

arr.push(4);

console.log(arr); // [1, 2, 3, 4]
console.log(copiedArr); // [1, 2, 3, 4] 
console.log(destructuredArr); // [1, 2, 3]
var obj = {};
var copiedObj = obj;
var destructuredObj = { ...obj };

obj.name = 'chun';

console.log(obj); // {name: "chun"}
console.log(copiedObj); // {name: "chun"}
console.log(destructuredObj); // {}

원본데이터의 수정을 막기

Object.freeze라는 메소드를 이용하여 값의 변경을 막아버림으로써 Immutable하게 만든다.

var person = {
    name: {
        first: 'Jangchun',
        last: 'Lee'
    },
    age: 30,
};

Object.freeze(person);

person.age = 29; // ignored
person.name.last = 'Park'; // applied ?? 

console.log(person);
/*
{
    name: {
        first: "Jangchun",
        last: "Park"
    },
    age: 30
}
*/

이상한 점이 하나 있다.

person.age 코드는 무시가 된 반면, person.name.last = 'Park'; 적용이 되었다.
person은 수정을 방지했지만, person.name은 Object로 mutable data 이기때문에, 값의 변경이 가능해졌다. 이를 막으려면 내부객체까지 일일이 freeze 시켜야한다.

이러한 deep freeze 이슈는 앞서 소개한 모든 방법들에 공통적으로 발생하는 이슈로, 이러한 작업들을 쉽게 해주는 라이브러리들이 존재한다.


Immutable.js

위에서 말한 deep freeze와 같은 번거로움들을 쉽게 해주는 다양한 library들이 있는데,
현재 가장 인기가 많은 라이브러리인 Immutable.js 의 간단한 사용법에 대해 소개한다.

설치

$ npm install immutable

사용

  1. immutable.js가 제공하는 데이터 구조(Stack, List, Map, OrderedMap...)객체를 생성한다.
  2. 조작한다. 조작할때마다 하든 Immutable 하게 새로운 값을 생성한다.
const { Map } = require('immutable');

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

map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
const { List } = require('immutable');

const list1 = List([ 1, 2 ]);
const list2 = list1.push(3, 4, 5);
const list3 = list2.unshift(0);
const list4 = list1.concat(list2, list3);

console.log(list1.size); // 2
console.log(list2.size); // 5
console.log(list3.size); // 6
console.log(list4.size); // 13

왜 중요할까?

2-way-bind를 지원하는 SPA 프레임워크에서는 옵저버패턴이 자주 사용된다.
옵저버패턴은 옵저버가 특정 주제(데이터)를 감시하다가 값이 변경되었을 때를 감지할 수 있는 디자인 패턴이다.

자바스크립트가 데이터가 변경이 감지하는 시점은 변수가 참조하는 메모리의 주소가 변경되었을때를 의미한다.
따라서 mutable한 데이터의 변경은 기존 메모리 영역에 있는 데이터를 수정하기 때문에, 옵저버가 데이터의 변경을 인식하지 못하게 되는 경우가 발생한다.

그렇기에 Immutability를 이해하고 적용하는건 중요하다.


참고문헌

profile
오늘 먹은 음식도 기억이 안납니다. 그래서 모든걸 기록합니다.
post-custom-banner

0개의 댓글