如下代码会用到函数组合函数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...