-
How to make the Object as immutable in JSFP 2020. 7. 9. 01:00반응형
출처: https://medium.com/@neetishop/immutable-objects-with-property-descriptors-in-javascript-31693faaf03
불변
FP에서 객체의 상태를 불변으로 유지하는건 굉장히 중요하다.
불변을 통해 부수효과를 방지하기 때문이다.Why JS?
간단하다. 굉장히 범용적으로 사용되는 언어이고
무엇보다 내가 좋아하기 때문이다.-끝-
농담이다. 🙃
JS에서 객체의 상태 관리
ES5까지는 변수를 상수처럼 고정 할 수 있는 방법이 없었지만
ES6에서const
라는 멋들어진 키워드가 소개되면서 우리는 변수를 상수처럼
런타임시에 변경할 수 없도록 지정할 수 있게 되었다.하지만 그것도 잠시...
객체의 속성은 여전히 런타임에도 맘데로 지멋데로 제멋데로 바꿀 수 있다.
속성의 변신은 무죄?function Dog(name) { this.name = name; } Dog.prototype.toString = function() { return `Dog(name: '${this.name}')`; } const dog = new Dog('hello'); dog.name = 'world'; dog.toString(); // Dog(name: 'world') ...!!!!!!
이런 거지같은 상황을 타개할 방도가 정녕 없단말인가?
신에게는 아직 클로저가 있사오니...
그렇다. 우리는 클로저(Closure)라는 굉장한 무기가 있었다.
바로 클로저를 통해 객체지향의private
한 느낌으로 변수를 관리해서 속성의 변신을 유죄 로 만들어 버릴 수 있다.function Dog(name) { const _name = name; return { name: _name, toString: function() { return `Dog(name: '${_name}')`; } }; } const dog = new Dog('hello'); dog.name = 'world'; dog.toString(); // Dog(name: 'hello')
너가 아무리 우리 객체한테 찍접거려도
우리 객체는 쳐다도 안본단다. 😝즉, 클로저를 통해 객체 내부의 변수로의 접근을 차단해버릴 수 있다.
그리고 한가지 또!
만약 객체 내부의 값을 바꿔야 한다면, 새로운 객체를 만들어 반환하여 불변으로 유지할 수 있다.function Developer(coffee) { const code = coffee ? 'more code!' : 'no more code...'; return { drink: function(coffee) { return new Developer(coffee); }, toString: function() { return `I can ${code}`; } }; } const huna = new Developer('coffee'); const tiredHuna = huna.drink(null); huna.toString(); // I can more code! tiredHuna.toString(); // I can no more code...
굉장하지 않는가?
출처: https://www.reddit.com/r/ProgrammerHumor/comments/8n0b10/i_prefer_monster_tbh/
또 다른 방법
우리에겐 사실 클로저 말고 다른 무기가 하나 더 있었다.
다만, 당신이 API 문서를 대충 봐서 놓친 그 무기 말이다.바로 Object.freeze() 이다.
function Dog(name) { this.name = name; } Dog.prototype.toString = function() { return `Dog(name: '${this.name}')`; } const dog = Object.freeze(new Dog('hello')); dog.name = 'world'; dog.toString(); // Dog(name: 'hello')
오... 클로저랑 동일하게 동작한다.
근데 좀 그런 문제가 하나 있는데, 요건 사실 얕은 동결(shallow freeze) 이라고 불린다.
그게 뭐냐면 역시 코드로 한번 보자.function Master(name) { this.name = name || 'No master'; } Master.prototype.toString = function() { return `Master(name: '${this.name}')`; } function Dog(name, master) { this.name = name; this.master = master instanceof Master ? master : new Master(); } Dog.prototype.toString = function() { return `Dog(name: '${this.name}', master: ${this.master.toString()})`; } const dog = Object.freeze(new Dog('js')); dog.toString(); // Dog(name: 'js', master: Master(name: 'No master')) dog.master.name = 'huna'; dog.toString(); // Dog(name: 'js', master: Master(name: 'huna'))... WTF!!!
아직 실망하긴 이르다.
왜냐하면 우린 JS 용사들이기 때문이다.
재귀를 통해 객체의 속성들을 전부 얼려버릴 수 있지 않나요?
코드로 한번 구현해보자.const isObject = val => val && typeof val === 'object'; function deepFreeze(obj) { if (isObject(obj) && !Object.isFrozen(obj)) { Object.keys(obj).forEach(name => deepFreeze(obj[name])); Object.freeze(obj); } return obj; } // 위의 Dog 객체를 사용하자 const dog = deepFreeze(new Dog('FrozenJS')); dog.master.name = 'huna'; dog.toString(); // Dog(name: 'FrozenJS', master: Master(name: 'No master'))
굉장하다. 재귀 만세.
함수를 쓰다가 화살표 함수를 쓰다가 왔다갔다 하는건 우린 ES6의 축복을 받았기 때문에 맘데로 해본 것 뿐이다.
여기가 끝인줄 알았다면 오산이다.
아직 FP스럽게 객체를 동결하는 무시무시한 무기가 또 남아있다.
The 렌즈
렌즈는 뭘까? 카메라에 달려있는 그거?
뭔가를 들여다 보는 걸까?그렇다. 역시 당신은 JS 용사다.
렌즈를 통해 원하는 속성에만 집중 할 것이다.아래 예제에서는 유명한 함수형 라이브러리인 ramda 를 사용해서 렌즈 기법을 살펴볼 것이다.
보통 ramda는
R
이라는 전역 객체를 통해 모든 함수들에 접근이 가능하다.사용법은 먼저
- 우리가 접근하려는 속성을 렌즈로 만들고,
- 렌즈를 통해 들여다보거나
- 렌즈를 통해 수정할 수 있다.
이를 통해 우리는 불변 객체를 사용할 수 있다.
const dog = new Dog('js'); dog.master = new Master('huna'); const nameLens = R.lensProp('name'); const newDog = R.set(nameLens, 'FP', dog); dog.toString(); // Dog { name: 'js', master: Master { name: 'huna' } } newDog.toString(); // Dog { name: 'FP', master: Master { name: 'huna' } }
또한 굳이 귀찮게
deepFreeze
같은 함수 안만들고도
객체의 속성의 객체의 속성들 같은 중첩된 객체들까지 모조리 불변으로 만들어 버릴 수 있다.const masterLens = R.lensPath(['master', 'name']); const cryingDog = R.set(masterLens, 'others', dog); dog.toString(); // Dog(name: 'js', master: Master(name: 'huna')) cryingDog.toString(); // Dog(name: 'js', master: Master(name: 'others'))
굉장하다...
deepFreeze
꺼졍 두번 꺼졍또한 렌즈는
setter
로써의 역할뿐만 아니라getter
의 역할도 아주 기똥차게 해낸다.R.view(nameLens, dog); // 'js' R.view(masterLens, dog); // 'huna'
어떤가. 프로젝트에서 렌즈를 써보고 싶어서 손가락이 근질근질 하지 않는가?
그럼 이만!
출처: 함수형 자바스크립트
반응형'FP' 카테고리의 다른 글
Function (0) 2020.07.12 FP vs OOP (0) 2020.07.08 Initial commit - FP (0) 2020.07.07