如下代碼會用到函數組合函數compose,只要知道compose是幹什麼的就足夠了,若是好奇具體的實現,能夠看《JavaScript函數式編程之函數組合函數compose和pipe的實現》javascript
管道是函數式編程中常常使用的,不少時候咱們須要按照條件判斷進行組合函數的選擇,簡單的說就是從原來的一條管道變成兩條管道,根據判斷選擇進入哪一條。html
這裏的關鍵在於,咱們如何判斷上一個函數的返回值應該進入哪一條管道?java
let step1 = x => x ? 1 : 2; let step2 = x => x === 1 ? 3 : 4; let step3 = x => x === 3 ? 5 : 6; let getResult = compose(step3, step2, step1) let result = getResult(1);
這是最直接的方法,每一步根據返回值單獨判斷,若是step1的返回值發生了變化,下一步的判斷也須要跟着修改,這種寫法顯然很差。那咱們能不能在返回值的基礎上加上一個標識,專門用來作判斷?咱們很天然的就會想到對象。ios
let step1 = x => { if (x) { return { value: 1, identity: 'channelOne' } } else { return { value: 2, identity: 'channelTwo' } } } let step2 = x => { if (x.identity === 'channelOne') { return x.value = 3; } else { return x.value = 4; } } let step3 = x => { if (x.identity === 'channelOne') { return x.value = 5; } else { return x.value = 6; } } let getResult = compose(step3, step2, step1); let result = getResult(1);
是否是好了不少?不過這依然要繼續改進。當咱們須要使用forin
等方式遍歷對象時,identity會被遍歷出來,通常狀況下咱們都但願它不會被遍歷,那就還須要把這個屬性定義爲不可枚舉的。
修改step1並簡化代碼:git
let step1 = x => { if (x) { let obj = {value: 1}; Object.defineProperty(obj, 'identity', { enumerable: false, value: 'channelOne' }); return obj; } else { let obj = {value: 2}; Object.defineProperty(obj, 'identity', { enumerable: false, value: 'channelTwo' }); return obj; } } let selectChannel = (fn1, fn2) => val => val.identity === 'channelOne' ? fn1(val) : fn2(val); let getResult = compose( selectChannel( x => Object.defineProperty(x, 'value', {value: 5}), x => Object.defineProperty(x, 'value', {value: 6}) ), selectChannel( x => Object.defineProperty(x, 'value', {value: 3}), x => Object.defineProperty(x, 'value', {value: 4}) ), step1 ); let result = getResult(1);
在selectChannel中,函數會根據傳進來的對象的標識選擇執行。至此,功能基本上實現了,可依然不夠好,代碼不夠簡潔優雅,重用也能夠繼續改進。github
用構造函數作標識編程
let channelOne = function(x) { this.value = x; }; channelOne.of = x => new channelOne(x); let channelTwo = function(x) { this.value = x; }; channelTwo.of = x => new channelTwo(x); let step1 = x => x ? channelOne.of(1) : channelTwo.of(2); let selectChannel = (fn1, fn2) => val => val.constructor === channelOne ? fn1(val) : fn2(val); let getResult = compose( selectChannel(x => channelOne.of(5), x => channelTwo.of(6)), selectChannel(x => channelOne.of(3), x => channelTwo.of(4)), step1 ); let result = getResult(1);
太棒了!
看到這裏,有麼有驚喜的發現,if/else
不見了?確定會有人以爲我是一個換湯不換藥的奸商。雖然if/else
不見了,但是我用了三元運算符,這在本質上有什麼區別?
答案是,沒區別,他們都是條件判斷,這是不可避免的。
咱們不妨暫時把關注的焦點放在三元運算符與if/else
的區別上面來。咱們何時會使用三元運算符?是條件判斷很簡單的時候,簡單到只須要一個表達式,而不是複雜的操做。雖然三元運算符也能夠用逗號隔開表達式從而進行多個操做,可咱們這個時候更願意使用if/else
。
說到這裏就已經很明顯了,這種構造函數作標識的方式,把複雜的條件判斷分解了,分解到在作判斷的時候只須要選擇方向,相關的操做能夠扔到後面。axios
在《JavaScript函數式編程中的錯誤處理,強壯代碼》文章中所用的思路與本篇同樣,只不過在《JavaScript函數式編程中的錯誤處理,強壯代碼》中能夠認爲是以null
和undefined
做爲標識,而本篇單首創造了標識。本篇中的方法更加的通用,由於null
和undefined
也多是咱們須要使用的值。數組
使用本篇的方法重寫《JavaScript函數式編程中的錯誤處理,強壯代碼》中的代碼ide
let channelError = function(x) { this.value = x; }; channelError.of = x => new channelError(x); let channelSuccess = function(x) { this.value = x; }; channelSuccess.of = x => new channelSuccess(x); let security= fn => val => val.constructor === channelError? val : fn(val); let validate1 = x => x ? channelSuccess.of(x) : channelError.of('validate1 is not passed!'); let validate2 = x => x.value ? x : channelError.of('validate2 is not passed!'); let handleError = x => { if (x.constructor === channelError) alert(x.value); }; let postData = () => axios.post(...); let getResult = compose( handleError, security(postData), security(validate2), security(validate1) );
參考資料:
我在github https://github.com/zhuanyongx...