以下代碼會用到函數組合函數compose,只需曉得compose是干什麼的就足夠了,假如獵奇詳細的完成,能夠看《JavaScript函數式編程之函數組合函數compose和pipe的完成》
管道是函數式編程中常常運用的,許多時刻我們須要依據前提推斷舉行組合函數的挑選,簡樸的說就是從本來的一條管道變成兩條管道,依據推斷挑選進入哪一條。
這裏的關鍵在於,我們怎樣推斷上一個函數的返回值應當進入哪一條管道?
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的返回值發生了變化,下一步的推斷也須要隨着修正,這類寫法明顯不好。那我們能不能在返回值的基礎上加上一個標識,特地用來做推斷?我們很天然的就會想到對象。
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並簡化代碼:
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中,函數會依據傳進來的對象的標識挑選實行。至此,功用基本上完成了,可依舊不夠好,代碼不夠簡約文雅,重用也能夠繼承革新。
用組織函數做標識
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
。
說到這裏就已很明顯了,這類組織函數做標識的體式格局,把龐雜的前提推斷剖析了,剖析到在做推斷的時刻只須要挑選方向,相干的操縱能夠扔到背面。
在《JavaScript函數式編程中的錯誤處理,強健代碼》文章中所用的思緒與本篇一樣,只不過在《JavaScript函數式編程中的錯誤處理,強健代碼》中能夠以為是以null
和undefined
作為標識,而本篇零丁製造了標識。本篇中的要領越發的通用,由於null
和undefined
也多是我們須要運用的值。
運用本篇的要領重寫《JavaScript函數式編程中的錯誤處理,強健代碼》中的代碼
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…