JavaScript函數式編程之管道分支,消除if/else的一種方式

如下代碼會用到函數組合函數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函數式編程中的錯誤處理,強壯代碼》中能夠認爲是以nullundefined做爲標識,而本篇單首創造了標識。本篇中的方法更加的通用,由於nullundefined也多是咱們須要使用的值。數組

使用本篇的方法重寫《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)
);

參考資料:

JS函數式編程指南

我在github https://github.com/zhuanyongx...

相關文章
相關標籤/搜索