Immutability - wooyeonhui/Yeonny GitHub Wiki
Immutability(๋ณ๊ฒฝ๋ถ๊ฐ์ฑ)๋ ๊ฐ์ฒด๊ฐ ์์ฑ๋ ์ดํ ๊ทธ ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ ๋์์ธ ํจํด์ ์๋ฏธํ๋ค.Immutability์ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ํต์ฌ ์๋ฆฌ์ด๋ค.
๊ฐ์ฒด๋ ์ฐธ์กฐ(reference) ํํ๋ก ์ ๋ฌํ๊ณ ์ ๋ฌ ๋ฐ๋๋ค. ๊ฐ์ฒด๊ฐ ์ฐธ์กฐ๋ฅผ ํตํด ๊ณต์ ๋์ด ์๋ค๋ฉด ๊ทธ ์ํ๊ฐ ์ธ์ ๋ ์ง ๋ณ๊ฒฝ๋ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋ ๊ฐ๋ฅ์๋ ์ปค์ง๊ฒ ๋๋ค. ์ด๋ ๊ฐ์ฒด์ ์ฐธ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ด๋ค ์ฅ์์์ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์ฐธ์กฐ๋ฅผ ๊ณต์ ํ๋ ๋ชจ๋ ์ฅ์์์ ๊ทธ ์ํฅ์ ๋ฐ๊ธฐ ๋๋ฌธ์ธ๋ฐ ์ด๊ฒ์ด ์๋ํ ๋์์ด ์๋๋ผ๋จ ์ฐธ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ๋ค๋ฅธ ์ฅ์์ ๋ณ๊ฒฝ ์ฌ์ค์ ํต์งํ๊ณ ๋์ฒํ๋ ์ถ๊ฐ ๋์์ด ํ์ํ๋ค.
์๋ํ์ง ์์ ๊ฐ์ฒด์ ๋ณ๊ฒฝ์ด ๋ฐ์ํ๋ ์์ธ์ ๋๋ค์๋ โ๋ ํผ๋ฐ์ค๋ฅผ ์ฐธ์กฐํ ๋ค๋ฅธ ๊ฐ์ฒด์์ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝโํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ด ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋น์ฉ์ด ์กฐ๊ธ ๋ค์ง๋ง ๊ฐ์ฒด๋ฅผ ๋ถ๋ณ๊ฐ์ฒด๋ก ๋ง๋ค์ด ํ๋กํผํฐ์ ๋ณ๊ฒฝ์ ๋ฐ์งํ๋ฉฐ ๊ฐ์ฒด์ ๋ณ๊ฒฝ์ด ํ์ํ ๊ฒฝ์ฐ์๋ ์ฐธ์กฐ๊ฐ ์๋ ๊ฐ์ฒด์ ๋ฐฉ์ด์ ๋ณต์ฌ(defensive copy)๋ฅผ ํตํด ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ฃจ ๋ณ๊ฒฝํ๋ค. ๋๋ Observerํจํดใน์ผ๋ก ๊ฐ์ฒด์ ๋ณ์ ฉ์ ๋์ฒํ ์๋ ์๋ค.
๋ถ๋ณ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ๋ณต์ ๋ ๋น๊ต๋ฅผ ์ํ ์กฐ์์ ๋จ์ํ ํ ์ ์๊ณ ์ฑ๋ฅ ๊ฐ์ ์๋ ๋์์ด ๋๋ค. ํ์ง๋ง ๊ฐ์ฒด๊ฐ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๋ฐ์ดํฐ๋ฅผ ๋ง์ด ๊ฐ์ง๊ณ ์๋ ๊ฒฝ์ฐ ์คํ๋ ค ๋ถ์ ์ ํ ๊ฒฝ์ฐ๊ฐ ์๋ค.
ES6์์๋ ๋ถ๋ณ ๋ฐ์ดํฐ ํจํด(immutable data pattern)์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ ์๋ก์ด ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์๋ค.
## immutable value vs. mutable value javascript์ ๊ธฐ๋ณธ ์๋ฃํ(primitive data type)์ ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ ๊ฐ(immutable value)์ด๋ค.
* Boolean * null * unsefined * Number * String * Symbol(New in ECMAScript 6)
๊ธฐ๋ณธ ์๋ฃํ ์ด์ธ์ ๋ชจ๋ ๊ฐ์ ๊ฐ์ฒด(Object) ํ์ ์ด๋ฉฐ ๊ฐ์ฒด ํ์ ์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๊ฐ(mutable value)์ด๋ค.์ด๋ฐ ๊ฐ์โprimitive valuesโ๋ผ ํ๋ค.(๋ณ๊ฒฝ์ด ๋ถ๊ฐ๋ฅํ๋ค๋ ๋ป์ ๋ฉ๋ชจ๋ฆฌ ์์ญ์์์ ๋ณ๊ฒฝ์ด ๋ถ๊ฐ๋ฅํ๋ค๋ ๋ป์ด๋ค. ์ฌํ ๋น์ ๊ฐ๋ฅํ๋ค)
var statement = โI am an immutable valueโ; // string์ immutable value
var otherStr = statement.slice(8, 17);
console.log(otherStr); // โimmutableโ console.log(statement); // โI am an immutable valueโ
2ํ์์ String๊ฐ์ฒด์ slice()๋ฉ์๋๋ statement ๋ณ์์ ์ ์ฅ๋ ๋ฌธ์์ด์ ๋ณ๊ฒฝํ๋ ๊ฒ์ด ์๋๋ผ ์ฌ์ค์ ์๋ก์ด ๋ฌธ์์ด์ ์์ฑํ์ฌ ๋ฐํํ๊ณ ์๋ค. ๊ทธ ์ด์ ๋ ๋ฌธ์์ด์ ๋ณ๊ฒฝํ ์ ์๋ immutable value์ด๊ธฐ ๋๋ฌธ์ด๋ค.
var arr = []; console.log(arr.length); // 0
var v2 = arr.push(2); // arr.push()๋ ๋ฉ์๋ ์คํ ํ arr์ length๋ฅผ ๋ฐํconsole.log(arr.length); // 1
์๊ธฐ ์์ ์์ v2์ ๊ฐ์ ๋ฌด์์ธ๊ฐ? ๋ฌธ์์ด์ ์์ ๊ฐ์ด ๋ฐฐ์ด์ด ๋์ํ๋ค๋ฉด v2๋ ์๋ก์ด ๋ฐฐ์ด(ํ๋์ ์์๋ฅผ ๊ฐ์ง๊ณ ๊ทธ ๊ฐ์ 2์ธ)์ ๊ฐ์ง๊ฒ ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๋ ๊ฐ์ฒด์ธ arr์ push๋ฉ์๋์ ์ํด update๋๊ณ v2์๋ ๋ฐฐ์ด์ ์๋ก์ด length ๊ฐ์ด ๋ฐํ๋๋ค.
์ฒ๋ฆฌ ํ ๊ฒฐ๊ณผ์ ๋ณต์ฌ๋ณธ์ ๋ฆฌํดํ๋ ๋ฌธ์์ด์ ๋ฉ์๋ slice()์๋ ๋ฌ๋ฆฌ ๋ฐฐ์ด(๊ฐ์ฒด)์ ๋ฉ์๋ push()๋ ์ง์ ๋์ ๋ฐฐ์ด์ ๋ณ๊ฒฝํ๋ค. ๊ทธ ์ด์ ๋ ๋ฑ์ด์ ๊ฐ์ฒด์ด๊ณ ๊ฐ์ฒด๋ immutable value๊ฐ ์๋ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๋ค๋ฅธ ์๋ฅผ ์์๋ณด์.
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์ ๊ฐ์ ๋ณ๊ฒฝํ์ง๋ง ๋ณ์ myName์ ๊ฐ์ ๋ณ๊ฒฝ๋์ง ์๋๋ค. ์ด๋ user.name์ ํ์ string์ด ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ด๋ค.
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 ํ๋กํผํฐ์ ์๋ก์ด ๊ฐ์ ํ ๋นํ๋ฉด ๊ฐ์ฒด๋ ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ ๊ฐ์ด ์๋๋ฏ๋ก ๊ฐ์ฒด user2๋ ๋ณ๊ฒฝ๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ณ๊ฒฝํ์ง๋ ์์ ๊ฐ์ฒด user1๋ ๋์์ ๋ณ๊ฒฝ๋๋ค. ์ด๋ user1๊ณผ user2๊ฐ ๊ฐ์ ์ด๋๋ ์ค๋ฅผ ์ฐธ์กฐํ๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์๋ํ์ง ์์ ๊ฐ์ฒด์ ๋ณ๊ฒฝ์ด ๋ฐ์ํ๋ ์์ธ์ ๋๋ค์๋ โ๋ ํผ๋ฐ์ค๋ฅผ ์ฐธ์กฐํ ๋ค๋ฅธ ๊ฐ์ฒด์์ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝโํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ด ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋น์ฉ์ ์กฐ๊ธ ๋ค์ง๋ง ๊ฐ์ฒด๋ฅผ ๋ถ๋ณ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ํ๋กํผํฐ์ ๋ณ๊ฒฝ์ ๋ฐฉ์งํ๋ฉฐ ๊ฐ์ฒด์ ๋ณ๊ฒฝ์ด ํ์ํ ๊ฒฝ์ฐ์๋ ์ฐธ์กฐ๊ฐ ์๋ ๋ฐฉ์ด์ ๋ณต์ฌ(defensive copy)๋ฅผ ํตํด ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ ํ ํ ๋ณ๊ฒฝํ๋ค.
์ด๋ฅผ ์ ๋ฆฌํ๋ฉด ์๋์ ๊ฐ๋ค.
* ๊ฐ์ฒด์ ๋ฐฉ์ด์ ๋ณต์ฌ(defensive copy) Object.assign * ๋ถ๋ณ๊ฐ์ฒดํ๋ฅผ ํตํ ๊ฐ์ฒด ๋ณ๊ฒฝ ๋ฐฉ์ง Object.freeze
Object.assign์ ํํท ๊ฐ์ฒด๋ก ์์ค ๊ฐ์ฒด์ ํ๋กํผํฐ๋ฅผ ๋ณต์ฌํ๋ค. ์ด๋ ์์ค ๊ฐ์ฒด์ ํ๋กํผํฐ์ ๋์ผํ ํ๋กํผํฐ๋ฅผ ๊ฐ์ง ํ์ผ ๊ฐ์ฒด์ ํ๋กํผํฐ๋ค์ ์์ค ๊ฐ์ฒด์ ํ๋กํผํฐ๋ก ๋ฎ์ด์ฐ๊ธฐ๋๋ค. ๋ฆฌํด๊ฐ์ผ๋ก ํํท ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค. ES6์์ ์ถ๊ฐ๋ ๋ฉ์๋์ด๋ฉฐ Internet Explorer๋ ์ง์ํ์ง ์๋๋ค.
// Syntax Object.assign(target, โฆsources)
// 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์ ์ฌ์ฉํ์ฌ ๊ธฐ์กด ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
const user1 = {
name: 'Lee', address: { city: 'Seoul' }
};
const user2 = Object.assign({}, user1); // user1์ {}์ Copy
user2.name = โKimโ;
// ์๊ธฐ 2ํ์ ์๋์ ๋์น์ด๋ค. // {name: โKimโ}์ user1์ ๋ณํฉ๋๋ ๊ฒ์ด ์๋๋ผ ์ฒซ๋ฒ์งธ ์ธ์์ธ {}์ ๋ณํฉ๋๋ค. // const user2 = Object.assign({}, user1, {name: โKimโ});
console.log(user1.name); // Lee console.log(user2.name); // Kim
user1 ๊ฐ์ฒด๋ฅผ ๋น๊ฐ์ฒด์ ๋ณต์ฌํ์ฌ ์๋ก์ด ๊ฐ์ฒด user2๋ฅผ ์์ฑํ์๋ค. user1๊ณผ user2๋ ์ด๋๋ ์ค๋ฅผ ๊ณต์ ํ์ง ์์ผ๋ฏ๋ก ํ ๊ฐ์ฒด๋ฅผ ๋ณ๊ฒฝํ์ฌ๋ ๋ค๋ฅธ ๊ฐ์ฒด์ ์๋ฌด๋ฐ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
์ฃผ์ํ ๊ฒ์ user1 ๊ฐ์ฒด๋ const๋ก ์ ์ธ๋์ด ์ฌํ ๋น์ ํ ์ ์์ง๋ง ๊ฐ์ฒด์ ํ๋กํผํฐ๋ ๋ณดํธ๋์ง ์๋๋ค. ๋ค์ ๋งํ์๋ฉด ๊ฐ์ฒด์ ๋ด์ฉ์ ๋ณ๊ฒฝํ ์ ์๋ค.
Object.freeze()๋ฅผ ์ฌ์ฉํ์ฌ ๋ถ๋ณ(immutable) ๊ฐ์ฒด๋ก ๋ง๋ค์ ์๋ค.
const user1 = {
name: 'Lee', address: { city: 'Seoul' }
};
const user2 = Object.assign({}, user1, {name: โKimโ});
console.log(user1.name); // Lee console.log(user2.name); // Kim
Object.freeze(user1);
user1.name = โKimโ; // ๋ฌด์๋๋ค!
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โ } }
๋ด๋ถ ๊ฐ์ฒด๊น์ง ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๋ ค๋ฉด 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: 'Lee', address: { city: 'Seoul' }
};
deepFreeze(user);
user.name = โKimโ; // ๋ฌด์๋๋คuser.address.city = โBusanโ; // ๋ฌด์๋๋ค
console.log(user); // { name: โLeeโ, address: { city: โSeoulโ } }
Object.assign๊ณผ Object.freeze์ ์ฌ์ฉํ์ฌ ๋ถ๋ณ ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ฒ๊ฑฐ๋ฌ์ธ ๋ฟ๋๋ฌ ์ฑ๋ฅ์ ์ด์๊ฐ ์์ด์ ํฐ ๊ฐ์ฒด์๋ ์ฌ์ฉํ์ง ์๋ ๊ฒ์ด ์ข๋ค. ๋ ๋ค๋ฅธ ๋์์ผ๋ก Facebook์ด ์ ๊ณตํ๋ Immutable.js๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์๋ค. Immutable.js๋ List,Stack,Map,OrderedMap,Set,OrderedSet,Record์ ๊ฐ์ ์๊ตฌ ๋ถ๋ณ(Permit Immutable)๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํ๋ค. npm์ ์ฌ์ฉํ์ฌ 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