immutability 불변성

MNIII
6 min readMar 28, 2020

--

자바스크립트에서 중요한 개념으로 객체 불변성에 대해서 알아 보자.

객체는 참조(reference) 형태로 값을 주고 받는다. 하나의 객체가 생성되고 그 값을 다른 객체들이 참조 하고 있다면 의도 하지 않은 값의 변형으로 문제가 발생하는 경우가 있을 수 있다. 보통 이런 경우 레퍼런스를 참조한 다른 객체에서 객체를 변경 하기 때문이다. 이와 같은 문제를 방지 하기 위해서는 불변의(immutable) 객체를 만들거나 객체 복사를 통해 새로운 객체를 생성 한 후 변경하는 것이 안전하겠다.

Immutable value (primitives)

Boolean / String / Number / null / undefined

위 값들은 원시 타입(primitive type)으로 불변의 성격을 가지고 있다.

var x = 1 
var y = 'a'
x = 2
y = 'b'
console.log (x, y) // 2, 'b'

재 할당은 가능하다. 메모리 상에는 1, 'a' / 2, ‘b’ 모두 존재 하며 x, y 가 재 할당된 값을 가리키도록 변경된 것이다.

const x = 1 
x = 2
console.log (x) // 오류 ...
y = 3
y = 4 라고하자
console.log (y) // 4

ES6 에서는 const 는 재선언, 재할당이 불가능 하다.

const user = { name: 'Jack' }
user.name = 'John'
console.log(user) // { name: 'John' }

하지만 객체 내부까지 깊은 곳의 재할당 까지 제어 하지는 않는다.

var foo = 'immutable'
var boo = foo.replace('imm', 'm')
console.log(foo) // immutable
console.log(boo) // mutable

변수 foo의 값은 변하지 않는 원시타입 이기 때문에 replace() 메서드는 새로운 문자를 생성해서 반환 하였다고 볼 수 있다.

Mutable value

원시 타입의 값을 제외하고 나머지는 모두 객체 타입으로 새로운 값을 만들지 않고도 직접 변경이 가능 하다.

var arr = [1]
var arrCopy = arr
arrCopy.push(2)console.log(arr, arrCopy) // [1, 2], [1, 2]

배열은 객체이며 불변성을 가지고 있지 않다. 그래서 push() 메서드는 arrCopy가 참조 하고 있는 arr의 값도 직접적으로 영향을 준다.

var obj = { name: 'Jack' }
var objCopy = obj
objCopy.name = 'John'console.log(obj, objCopy) // { name: 'John' }, { name: 'John' }

이러한 결과는objobjCopy 가 참조하는 곳이 같기 때문이다.

대응 방법

Object.assign : 객체 참조가 아닌 객체 복사(cloning)

Object.freeze : 객체의 불변화를 통한 객체 변경 방지

Object.assign

Object.assign() 메서드는 타깃 객체의 속성만을 대상으로 객체로 복사 한다. (IE를 지원하지 않는다.)

// syntax
// Object.assign(target, ...obj)
const o1 = { a: 1 }
const o2 = { b: 2 }
const obj = Object.assign({}, o1, o2)console.log(obj) // { a: 1, b: 2 }
console.log(o1) // { a: 1 }
// 타깃 객체가 복사 됨const o3 = { a: 1 }
const o4 = { b: 2 }
const o5 = { c: 3 }
const obj1 = Object.assign(o3, o4, o5)console.log(obj1) // { a: 1, b: 2, c: 3 }
console.log(o3) // { a: 1, b: 2, c: 3 }
// 타깃 객체 자체가 변경됨

하지만 Object.assign() 메서드는 객체 내부의 객체가 있다면 완전한 복사를 하지 못한다는 것을 알아야 한다.

const user = {
name: 'Jack',
body: {
weight: 100
}
}
const userCopy = Object.assign({}, user)
console.log(user === userCopy)
// false - user와 userCopy의 참조는 다르다.
console.log(user.body === userCopy.body)
// true - 객체 내부 객체의 참조는 같다.
user.body.weight = 200

console.log(user.body.weight); // 200
console.log(userCopy.body.weight); // 200

Object.freeze

Object.freeze() 메서드를 사용하면 불변성의 객체를 만들 수 있다.

let user = { name: 'Jack' }Object.freeze(user)user.name = 'John' // 재할당 할 수 없다.console.log(user) // error...

user 가 불변 객체가 되어 재할당이 불가 하게 되었다. 하지만 Object.assign 와 마찬가지로 객체 내부의 객체에는 대응 하지 못한다.

const user = { 
name: 'Jack',
body: {
weight: 100
}
}
Object.freeze(user)user.body.weight = 200 // 재할당 가능하다.console.log(user) // { user: 'Jack', body: { weight: 200 } }

객체 내부의 객체까지 불변하게 만들려면 deep freeze 해야 한다.

function deepFreeze(obj) { 
const props = Object.getOwnPropertyNames(obj)
props.forEach((name) => {
const prop = obj[name]
if (typeof prop === 'object'&& prop! == null) {
deepFreeze(prop)
}
})
return Object.freeze(obj)
}
const user = {
name: 'Jack',
body: {
weight: 100
}
}
deepFreeze(user)user.name = 'Jack' // 재할당 불가
user.body.weight = 200 // 재할당 불가
console.log(user) // 오류

하지만 Object.assignObject.freeze을 사용하여 완전한 불변 객체를 만드는 방법은 추가 비용이 들기도 하고 성능상 이슈가 있어서 큰 객체에는 사용하지 않는 것이 좋다.

오픈소스

  • Immutable.js
  • lodash

--

--