객체와 변경불가성 (Immutable)

Dev_Oh·2022년 6월 13일
2
post-thumbnail

1. immutable value vs. mutable value

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

1.Boolean
2.null
3.undefined
4.Number
5.String
6.Symbol (New in ECMAScript 6)

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

예를 들어 살펴보자. C 언어와는 다르게 Javascript의 문자열은 변경 불가능한 값(immutable value) 이다. 이런 값을 “primitive values” 라 한다. (변경이 불가능하다는 뜻은 메모리 영역에서의 변경이 불가능하다는 뜻이다. 재할당은 가능하다)

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

내용을 입력하세요.
첫번째 실행시 메모리에 'Hello'라는 문자열을 저장 하고 str이 메모리 주소를 가리키는것

두번째 할당시 Hello를 수정하는 것이 아니라 새로 문자열을 메모리에 저장하고 str은 'world' 라는 메모리 주소를 가리키는것

즉, 'Hello' 와 'world'는 메모리에 모두 존재 한다

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

var myName = user.name; // 변수 myName은 string 타입이다.

user.name = 'Kim';
console.log(myName); // Lee

myName = user.name;  // 재할당
console.log(myName); // Kim

user.name = 'kim'; 으로 변경 했지만 변경되지 않았다 이는 immutable한 값 'Lee'가 메모리에 생성되고 myName은 이것을 참조하기 때문이다.

따라서 user.name 이 변경되더라도 'Lee'를 참조하고 있다
재할당을 통해 주소를 변경해줘야 한다.

var user1 = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

var user2 = user1; // 변수 user2는 객체 타입이다.

user2.name = 'Kim';

console.log(user1.name); // Kim
console.log(user2.name); // Kim

위 경우 user2.name 할당 시 user1,user2 둘다 수정된다.

이는 user1, user2가 같은 어드레스를 참조하고 있기 때문이다

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

의도치 않게 객체의 변경이 발생하는 원인은 대다수
레퍼런스 참조한 다른 객체에서 객체를 변경하기 때문이다

이문제를 해결 하려면 객체를 불변객체로 만들어 프로퍼티 변경을 방지하며, 객체의 변경이 필요한 경우 참조가 아닌 객체의 방어적 복사를 통해 새로운 객체를 생성한 후 변경한다

정리하면 아래와 같다

1.객체의 방어적 복사(defensive copy)
Object.assign
2.불변객체화를 통한 객체 변경 방지
Object.freeze

2.1 Object.assin

// Syntax
Object.assign(target, ...sources)

object.assign은 타킷 객체로 소스 객체의 프로퍼티를 복사한다.

이때 소스객체의 프로퍼티와 동일한 프로퍼티를 가진 타켓 객체의 프로퍼티들은 소스객체의 프로퍼티로 덮어쓰기 되다.

리턴값으로 타킷 객체를 반환 한다.
ES6에 추가된 개념이며, Internet Explorer를 지원하지 않는다

// Copy
const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }
console.log(obj == copy); // false

// Merge
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const merge1 = Object.assign(o1, o2, o3);

console.log(merge1); // { a: 1, b: 2, c: 3 }
console.log(o1);     // { a: 1, b: 2, c: 3 }, 타겟 객체가 변경된다!

// Merge
const o4 = { a: 1 };
const o5 = { b: 2 };
const o6 = { c: 3 };

const merge2 = Object.assign({}, o4, o5, o6);

console.log(merge2); // { a: 1, b: 2, c: 3 }
console.log(o4);     // { a: 1 }

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

const user1 = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

// 새로운 빈 객체에 user1을 copy한다.
const user2 = Object.assign({}, user1);
// user1과 user2는 참조값이 다르다.
console.log(user1 === user2); // false

user2.name = 'Kim';
console.log(user1.name); // Lee
console.log(user2.name); // Kim

// 객체 내부의 객체(Nested Object)는 Shallow copy된다.
console.log(user1.address === user2.address); // true

user1.address.city = 'Busan';
console.log(user1.address.city); // Busan
console.log(user2.address.city); // Busan

user1 참조한 객체가 수정되면 user2 할당된 객체도 프로퍼티가 수정되고,
user2 할당한 객체가 수정되도 user1 참조 객체는 수정되지 않는다

2.2 Object.freeze()

불변(immutable) 객체로 만들수 있다.

Object.freeze(객체);
const user1 = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

// Object.assign은 완전한 deep copy를 지원하지 않는다.
const user2 = Object.assign({}, user1, {name: 'Kim'});

console.log(user1.name); // Lee
console.log(user2.name); // Kim

Object.freeze(user1);

user1.name = 'Kim'; // 무시된다!
user.adress.city = 'Busan'; // 변경된다!

console.log(user1); // { name: 'Lee', address: { city: 'Seoul' } }

console.log(Object.isFrozen(user1)); // true

Object.freeze(user1);로 프로퍼티가 변경불가능 해진다.
하지만 user.adress.city 객체 내부의 객체(Nested Object)는 변경가능하다.

2.3 Immutable.js

object.assign, Object.freeze는 번거롭기 때문에 facebook에서 제공하는 Immutable.js를 설치하여 사용 하는 방법이 있다.

$ npm install immutable

Immutable.js의 Map 모듈을 임포트하여 사용한다.

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

map1.set(‘b’, 50)의 실행에도 불구하고 map1은 불변하였다. map1.set()은 결과를 반영한 새로운 객체를 반환한다.

참고문서: 모던 자바스크립트 Deep Dive

profile
웹개발이 재밌다. 8년차 웹퍼블리싱

0개의 댓글