JS 불변성

Voler Web·2022년 9월 6일

Word

목록 보기
17/23

불변성

mutate는 변화, mutable은 변화 가능한, mutability는 변화 가능성을 의미하는 단어입니다.

불변성(immutability)

  • 불변함을 의미하는 단어입니다.
  • 프로그래밍에서 immutability은 데이터의 원본이 훼손되는 것을 막는것을 의미합니다.

자바스크립트에서 불변성이란 객체가 생성된 이후 그 상태를 변경할 수 없는 것을 의미합니다.
여기서 상태를 변경할 수 있는 것과 값을 재할당하는 것은 다르다는 것을 알아야합니다.

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

위의 코드는 a에 10을 할당하고 b를 a가 가리키는 메모리 주소를 가리키게 합니다.
이때 a의 값을 20으로 변경시켜줍니다.

만약 값을 직접 변경하는 것이라면 a와 b가 둘다 20을 출력해야합니다.
하지만 자바스크립트에서 Number값은 불변성을 유지하기 때문에 새롭게 20이라는 값을 가지는 메모리 주소를 a에 할당하기 때문에 위와 같은 결과가 나오게 됩니다.


immutable vs mutable

자바스크립트에서는 불변성을 유지하는 값들과 그렇지 않은 값들이 나누어져 있습니다.

Bolean, Number, String, Null, undefined, Symbol과 같은 타입들은 불변성을 유지하는 타입들이고 Object타입들은 변경가능한 값들입니다.

이말은 객체는 객체 내부의 값을 변경하면 객체를 참조하고 있는 다른 값들도 다 같이 변경된다는 의미 입니다.

var obj = {
	name: 'voler',
}
var new_obj = obj;
obj.name = 'hello';

console.log(obj.name,new_obj.name); // 'hello' 'hello'

위와 같이 coke의 name 값을 변경하였는데 new_coke의 name까지 한번에 변경된 것을 볼 수 있습니다.


값을 불변하게 하는 방법

  • JavaScript에는 원시형 타입과, 객체형 타입을 이해할 필요가 있습니다.
  • 원시형 타입은 Number,String, Boolean, Null, Undefined, Symbol이 있습니다.
  • 객체형 타입은 Object, Array, Function이 있습니다.

원시형 타입

  • 원시형 타입은 선언되는 순간 값이 메모리에 저장되고, 변수가 그 값을 가리키게 됩니다.
  • 따라서 같은 값을 가리키는 두 변수를 동등 비교연산자(===)로 비교했을 때 true값이 반환됩니다.
    • 같은 값을 가리킨다는 것을 의미합니다.
var v1 = 1;
var v2 = 1;
console.log(v1 === v2) // true

객체형 타입

  • 객체형 타입은 선언되는 순간 값 자체가 아닌 새로운 메모리 주소를 생성해 저장하기 때문에 객체 별로 새로운 주소가 생성됩니다.
  • 따라서 동등 비교를 했을 경우 그 내용이 같더라도 false 값이 반환됩니다.
var obj1 = {
 name:'voler'
}
var obj2 = {
 name:'voler'
}
console.log(obj1 === obj2) // false
  • 원시형 타입은 값이 그 값 자체이므로, 값을 바꿀 수 없습니다. => 불변하다
  • 객체는 같은 객체라도 속성 값을 바꿀 수 있기 때문에 => 불변하지 않습니다 (가변성을 가집니다)

객체를 불변하게 만드는 방법

Object.assign(빈 객체, 복사하려는 객체)

var obj1 = { name: 'voler' }

var obj2 = Object.assign({}, obj1)

obj2.name = 'hello'

console.log(obj1.name); // 'voler'
console.log(obj2.name); // 'hello'

// obj1의 불변성은 유지됩니다.

Object.freeze(불변하게 만드려는 객체)

var obj1 = { name: 'voler' }
Object.freeze(obj1)
obj1.name = 'hello'
console.log(obj1) // voler
// 속성 값이 변경되지 않습니다.

Nested object(중첩된 객체)

  • Object.assign은 객체의 불변성을 지킬 수 있게 해주지만 Nested object의 불변성은 지켜지지 못합니다.
  • 만약 Nested object의 불변성도 유지하고 싶다면, 속성 내의 값도 복사를 해줘야 합니다.
var obj1 = { name: 'voler', score: [1, 2] }
var obj2 = Object.assign({}, obj1)
obj2.score = obj2.score.concat() // score Array의 불변성이 유지된다.

obj2.score.push(3)

console.log(obj1) // {name:'voler', score:[1,2]}
console.log(obj2) // {name:'voler', score:[1,2,3]}
  • freeze도 마찬가지로 nested 객체에 적용해야 합니다.

스프레드 문법 사용

var obj = {
 name: 'voler'
}
var new_obj = {...obj};
obj.name = 'hello';
console.log(obj.name,new_obj.name); // 'voler' 'hello'

스프레드 문법을 사용하여 객체를 복사하면 객체의 불변성을 유지 할 수 있습니다.
하지만 스프레드 문법은 1레벨 깊이에서만 유효하게 동작하기 때문에 객체 내부의 객체의 불변성까지는 유지 할 수 없습니다.

var obj = {
 name: 'voler'
 fake:{
  name:'hello'
 }
 var new_obj = {...obj};
 obj.fake.name = 'velog';
 console.log(obj.fake.name , new_obj.fake.name);
 // 'velog' 'velog'
}

만약 레벨 2 객체까지 불변성을 유지해주려면 아래와 같이 별도의 변수에 값을 재할당 하고 넣어주는 번거로운 과정을 거쳐야 합니다.

const obj = {
 name: 'voler'
 fake:{
  name: 'hello'
 }
 const new_fake = {...obj.fake};
 const new_obj = {...obj};
 
 new_obj.fake = new_fake;
 obj.fake.name = 'velog';
 console.log(obj.fake.name , new_obj.fake.name);
 // 'velog' , 'hello'
}

immer 라이브러리

불변성을 유지하기 위해 복잡한 과정 없이 immer 라이브러리를 사용하여 간단하게 불변성을 유지 할 수있씁니다.

  • 설치
    npm install immer
  • import
    import produce from 'immer'
  • 사용 방법
import produce from 'immer';
const obj = {
 name:'voler'
 fake:{
  name:'hello'
 }
}
const new_obj = produce(obj, (element) => {
   element.name = 'velog';
   element.fake.name = 'web';
})
console.log(obj.name , new_obj.name); // 'voler' 'velog'
console.log(obj.fake.name , new_obj.fake.name); 'hello' 'web'

structuredClone()

HTML spec에서는 'structuredClone()' 이라는 깊은 복사를 위한 글로벌 메서드를 제공하고 있습니다. 이 메서드의 기본 문법은 다음과 같습니다

structuredClone(value)
structuredClone(value, { transfer })
ParameterDescription
value복사할 원본 객체
transferAn array of transferable objects in value that

will be moved rather than cloned to the

returned object.

기본적인 사용법입니다

const obj = {
 name:'voler'
 fake:{
  name:'hello'
 }
}

const new_obj = structuredClone(obj);
new_obj.fake.name = bye;

console.log(obj) // { name : "voler" , fake { name:"hello" }}
console.log(new_obj) // { name : "voler" , fake { name:"bye" }}

간단하게 사용할 수 있는 메서드인것 같습니다.
최근에 거의 대부분의 브라우저에서 지원을 하기 시작하였다니 잔뜩 쓸 수 있을것 같다.

lodash _.clondeDeep()

사용 해 본적은 없지만 structuredClone()을 알게되었을 때 lodash 또한 알게 되었습니다.
불변성을 위해 깊은 복사를 지원하는 메서드이기에 추가하게 되었습니다. 객체 깊은복사 외에 다른 메서드들이 많아서 한번 정리 , 공부를 해봐야 될 것 같다.

클론딥 메서드의 기본 문법은 다음과 같습니다.

_.cloneDeep(value)

직관적으로 보기도 좋고 깔끔하고 사용하기 쉬운 메서드인것 같다.

사용예시

const _ = require('lodash');

const obj = {
 name:'voler'
 fake:{
  name:'hello'
 }
}

const new_obj = _.clondeDeep(obj);
new_obj.fake.name = bye;

console.log(obj) // { name : "voler" , fake { name:"hello" }}
console.log(new_obj) // { name : "voler" , fake { name:"bye" }}

결론

  • 원시타입은 값을 변경 할 때 , 참조하는 메모리 주소의 위치를 변경하는것이므로 값은 불변성을 유지합니다.
  • 객체타입은 값을 변경 할 때 메모리 주소에 있는 값을 변경하는 것이므로 불변성을 유지 할 수 없으며 , 불변성을 유지하기 위해서는 다른 메소드나 라이브러리등 사용해야 합니다.

출처 : co_mong.log
출처 : Serzhul`s Blog
출처 : geeksforgeeks

profile
공부하려 끄적이는 velog

0개의 댓글