객체와 변경불가성

원녕·2022년 11월 4일
1

1. immutable value vs. mutable value

Javascript의 원시 타입(primitive data type)은 변경 불가능한 값(immutable value)이다.

* 변경이 불가능하다는 뜻은 메모리 영역에서의 변경이 불가능하다는 뜻이다. 재할당은 가능하다

원시 타입 이외의 모든 값은 객체(Object) 타입이며 객체 타입은 변경 가능한 값(mutable value)이다. 즉, 객체는 새로운 값을 다시 만들 필요없이 직접 변경이 가능하다는 것이다.
아래의 코드를 살펴보자.

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

이때 문자열 ‘Hello’와 ‘world’는 모두 메모리에 존재하고 있다. 변수 str은 문자열 ‘Hello’를 가리키고 있다가 문자열 ‘world’를 가리키도록 변경되었을 뿐이다.

2행에서 Stirng 객체의 slice() 메소드는 statement 변수에 저장된 문자열을 변경하는 것이 아니라 사실은 새로운 문자열을 생성하여 반환하고 있다. 그 이유는 문자열은 변경할 수 없는 immutable value이기 때문이다.

상기 예제에서 v2의 값은 무엇인가? 문자열의 예와 같이 배열이 동작한다면 v2는 새로운 배열(하나의 요소를 가지고 그 값은 2인)을 가지게 될 것이다. 그러나 객체인 arr은 push 메소드에 의해 update되고 v2에는 배열의 새로운 length 값이 반환된다.

처리 후 결과의 복사본을 리턴하는 문자열의 메소드 slice()와는 달리 배열(객체)의 메소드 push()는 직접 대상 배열을 변경한다. 그 이유는 배열은 객체이고 객체는 immutable value가 아닌 변경 가능한 값이기 때문이다.

user.name의 값을 변경했지만 변수 myName의 값은 변경되지 않았다. 이는 변수 myName에 user.name을 할당했을 때 user.name의 참조를 할당하는 것이 아니라 immutable한 값 ‘Lee’가 메모리에 새로 생성되고 myName은 이것을 참조하기 때문이다. 따라서 user.name의 값이 변경된다 하더라도 변수 myName이 참조하고 있는 ‘Lee’는 변함이 없다.

위의 경우 객체 user2의 name 프로퍼티에 새로운 값을 할당하면 객체는 변경 불가능한 값이 아니므로 객체 user2는 변경된다. 그런데 변경하지도 않은 객체 user1도 동시에 변경된다. 이는 user1과 user2가 같은 어드레스를 참조하고 있기 때문이다.

이것이 의도한 동작이 아니라면 참조를 가지고 있는 다른 장소에 변경 사실을 통지하고 대처하는 추가 대응이 필요하다.

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

의도하지 않은 객체의 변경이 발생하는 원인의 대다수는 “레퍼런스를 참조한 다른 객체에서 객체를 변경”하기 때문이다.
이 문제의 해결 방법은 비용은 조금 들지만 객체를 불변객체로 만들어 프로퍼티의 변경을 방지하며 객체의 변경이 필요한 경우에는 참조가 아닌 객체의 방어적 복사(defensive이를 정리하면 아래와 같다.

  • 객체의 방어적 복사(defensive copy)
    Object.assign
  • 불변객체화를 통한 객체 변경 방지
    Object.freeze copy)를 통해 새로운 객체를 생성한 후 변경한다.

목표 객체의 속성 중 출처 객체와 동일한 키를 갖는 속성의 경우, 그 속성 값은 출처 객체의 속성 값으로 덮어씁니다. 출처 객체들의 속성 중에서도 키가 겹칠 경우 뒤쪽 객체의 속성 값이 앞쪽 객체의 속성 값보다 우선합니다. 즉,

  • [스시][오후 5:17] 결국 target 객체를 기준으로 해서 복사를한뒤
  • [스시][오후 5:18] 겹치는 내용만 후순위에 있는걸로 바꾼다

이런식의 설명이 될 수 있겠다.

Object.assign을 사용하여 기존 객체를 변경하지 않고 객체를 복사하여 사용할 수 있다. Object.assign은 완전한 deep copy를 지원하지 않는다. 객체 내부의 객체(Nested Object)는 Shallow copy된다.

구문

Object.assign(target, ...sources)

매개변수
target
목표 객체. 출처 객체의 속성을 복사해 반영한 후 반환할 객체입니다.
첫 번째 인자에는 무조건 목표 객체가 들어와야한다.
sources
출처 객체. 목표 객체에 반영하고자 하는 속성들을 갖고 있는 객체들입니다.
반환 값
목표 객체.

위 그림은 잘못된 예시는 아니지만 매개변수로 {}를 사용해서 o4,o5,o6이 제대로 복사가 되지 않은 상황이다.

Object.freeze()를 사용하여 불변(immutable) 객체로 만들수 있다.

Object.freeze

하지만 객체 내부의 객체(Nested Object)는 변경가능하다.

내부 객체까지 변경 불가능하게 만들려면 Deep freeze를 하여야 한다.

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

2.3 Immutable.js

Immutable.js는 List, Stack, Map, OrderedMap, Set, OrderedSet, Record와 같은 영구 불변 (Permit Immutable) 데이터 구조를 제공한다.

npm을 사용하여 Immutable.js를 설치한다.

$ npm install immutable

출처: https://poiemaweb.com/js-immutability

profile
메타인지하는 개발자

0개의 댓글