객체와 변경불가성(Immutability)

Sulhwa Choi·2022년 9월 30일
0

1. Object immutability (객체의 변경불가성)

  • 객체 생성 이후, 더 이상 변경불가능한 상태로 만드는 디자인 패턴을 의미한다.
  • 객체는 참조(Reference)를 통해 접근이 가능한 특성을 가져서 상태가 변경될 수 있다는 가능성이 있다.
  • ES5에서는 방어적 복사(defencive copy) 또는 Observer패턴으로 대처
  • 불변 객체로 만들게 되면 복제나 비교를 위한 작업이 단순화 되며, 성능 개선에 도움이 되지만, 객체가 변경이 가능한 데이터가 많을 경우 오히려 좋지 않아 주의해야한다.
  • ES6에서는 이를 불변 데이터 패턴(immutable data pattern)을 쉽게 구현하는 새로운 기능 추가
  • 자바스크립트에서 변경이 불가능하다는 의미는 메모리에 올라가 있는 데이터는 변경이 불가능하다는 것이고, 재할당은 가능하다.

2. immutable value(변경 불가능한 값) vs mutable value (변경 가능한 값)

① 원시타입 값 (값에 의한 전달) -> immutable value

📌 원시타입 (primitive data type)

  • Boolean
  • null
  • undefined
  • Number
  • String
  • Symbol(New in ECMAScript 6)

재할당 이외의 원시 값인 변수 값을 변경할 수 있다면 예기치않게 변수값이 변경될 수 있다는 것이고,
이는 값의 변경, 즉 상태 변경을 추적하기 어렵게 만든다.
따라서예상가능하고 신뢰할 수 있는 코드를 위해 원시 값은 불변


② 객체(참조)타입 값 (참조에 의한 전달) -> mutable value

📌 객체타입 (primitive data type)

  • Object
  • Array
  • Function

예기치 못한 변경이 발생하지 않도록 바로 값을 담는것이 아닌, 메모리 공간 주소를 담아 한 단계 더 거쳐 실제 객체에 접근함으로써 명확하고 신뢰성이 확보된다.


변수 : 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해서 붙인 이름 값 : 변수에 저장된 데이터로서 표현식이 평가되어 생성된 결과

변경 불가능하다는 것은 '변수'가 아닌 '값'이다.

(String)
var statement = 'I am an immutable value';
var otherStr = statement.slice(8, 17);

console.log(otherStr);   // 'immutable'
console.log(statement);  // 'I am an immutable value'
---------------------------------------------
(Array)
var arr = [];
console.log(arr.length); // 0

var v2 = arr.push(2);    // arr.push()는 메소드 실행 후 arr의 length를 반환
console.log(arr.length); // 1  
---------------------------------------------
 var user1 = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

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

user2.name = 'Kim';

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


✔️ 별도로 새로운 메모리에 할당 되는 것이 아니라 같은 곳을 바라보기 때문에 동시에 변경되고 의도된 코딩이 아니라면 막아야한다.

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

① Object.assign - 객체의 방어적 복사(defensive copy)

  • 타킷 객체로 소스 객체의 프로퍼티를 복사한다.
  • 소스 객체의 프로퍼티와 동일한 프로퍼티를 가진 타켓 객체의 프로퍼티들은 소스 객체의 프로퍼티로 덮어쓰기된다.
  • 리턴값으로 타킷 객체를 반환한다. ES6에서 추가된 메소드이며 Internet Explorer는 지원하지 않는다.

Object.assign(target, ...sources)

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

/*
user2가 할당한 객체가 수정되어도 user1의 참조 객체는 수정되지 않는다. 
user1이 참조한 객체를 수정하면 user2에 할당된 객체도 수정된다. 
*/

② Object.freeze - 불변객체화를 통한 객체 변경 방지

  • 타킷 객체로 소스 객체의 프로퍼티를 복사한다.
  • 소스 객체의 프로퍼티와 동일한 프로퍼티를 가진 타켓 객체의 프로퍼티들은 소스 객체의 프로퍼티로 덮어쓰기된다.
  • 리턴값으로 타킷 객체를 반환한다. ES6에서 추가된 메소드이며 Internet Explorer는 지원하지 않는다.

Object.freeze(obj)

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

--------------------------------------------------------- 

(객체 내부의 객체(Nested Object)는 변경가능)
const user = {
  name: 'Lee',
  address: {
    city: 'Seoul'
  }
};

Object.freeze(user);

user.address.city = 'Busan'; // 변경된다!
console.log(user); // { name: 'Lee', address: { city: 'Busan' } }

---------------------------------------------------------

(Nested Object에 대해서는 하나하나 중첩 안의 객체들에 대해서 freeze로 처리해 주어야 한다.  Deep freeze)  

function deepFreeze(object) {
  //Object.getOwnPropertyNames() 메서드:객체에 정의된 속성명을 추출하여 배열로 반환한다
  let props = Object.getOwnPropertyNames(object); //객체 key를 props에 저장

  // 스스로를 동결하기 전에 속성을 동결
  for(let name of props){ //배열의 length만큼 반복
  	let value=object[name];
    
    object[name]=value&&typeof value ==="object"?
    	deppFreeze(value):value;
  }
  return Object.freeze(object);
}

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

deepFreeze(user);

user.name = 'Kim';           // 무시된다
user.address.city = 'Busan'; // 무시된다

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


/*
user2가 할당한 객체가 수정되어도 user1의 참조 객체는 수정되지 않는다. 
user1이 참조한 객체를 수정하면 user2에 할당된 객체도 수정된다. 
*/


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

또 다른 대안으로 Facebook이 제공하는 Immutable.js를 사용하는 방법이 있다.

  • Immutable.js는 List, Stack, Map, OrderedMap, Set, OrderedSet, Record와 같은 영구 불변 (Permit Immutable) 데이터 구조를 제공한다.
profile
개발 공부 중 〰️ ٩(๑•̀o•́๑)و ✨

0개의 댓글