代码组合
在学习代码组合之前,我们需要回顾一下高阶函数的应用。
在高阶函数的章节学习中,我们探讨了一个实践中的案例。每一个页面都会判断用户的登录状态,因此我们封装了一个withLogin的高阶函数来处理这个统一的逻辑。而每一个页面的渲染函数,则作为基础函数,通过下面这样的方式得到高阶函数withLogin赋予的新能力。这个新的能力就是直接从参数中得到用户的登录状态。
window.renderIndex = withLogin(renderIndex);
但是如果这个时候,我们又新增加一个需求,我们不仅仅需要判断用户的登录状态,还需要判断用户打开当前页面所处的具体环境,是在某一个app中打开,还是在移动端打开,或者是在pc端的某一个浏览器中打开。因为不同的打开环境我们需要做不同的处理。
因此根据高阶函数的用法,我们还需要封装一个新的高阶函数withEnvironment来处理这个统一的环境判断逻辑。
(function() {
var env = {
isMobile: false,
isAndroid: false,
isIOS: false
}
var ua = navigator.userAgent;
env.isMobile = 'ontouchstart' in document;
env.isAndroid = !!ua.match(/android/);
env.isIOS = !!ua.match(/iphone/);
var withEnvironment = function(basicFn) {
return basicFn.bind(null, env);
}
window.withEnvironment = withEnvironment;
})();
那么正常情况下,我们在使用这个高阶函数时,就会这样做。
window.renderIndex = withEnvironment(renderIndex);
但是现在的问题是,我们这里已经有两个高阶函数想要给基础函数renderIndex传递新能力了。因为高阶函数的实现中我们使用了bind方法,因此withEnvironment(renderIndex)与renderIndex其实是拥有共同的函数体的,因此当遇到多个高阶函数时,我们也可以这样来使用。
window.renderIndex = withLogin(withEnvironment(renderIndex));
这样之后,我们就能够在renderIndex中接收到两个高阶函数带来的新能力了。但是这样是不是感觉很奇怪。因此为了避免这种多层嵌套使用的问题,我们可以使用代码组合的方式来解决。
我们期望有一个组合方法compose,可以这样来使用。参数从右至左,将第一个参数renderIndex作为第二个参数withEnvironment的参数,并将运行结果作为第三个参数withLogin的参数,依次递推。并最终返回一个新的函数。这个新函数,是在基础函数renderIndex的基础上,得到了所有高阶函数的新能力。
window.renderIndex = compose(withLogin, withEnvironment, renderIndex);
这样做之后呢,代码变得更加清晰直观,也不用担心更多的高阶组件进来增加嵌套。那么我们应该如何来实现这样一个compose函数呢?
// ...args 为ES6语法中的不定参数,args表示一个由所有参数组成的数组,最新的chrome浏览器已经支持该语法
function compose(...args) {
var arity = args.length - 1;
var tag = false;
if (typeof args[arity] === 'function') {
tag = true;
}
if (arity > 1) {
var param = args.pop(args[arity]);
arity--;
var newParam = args[arity].call(args[arity], param);
args.pop(args[arity]);
// newParam 是上一个参数的运行结果,我们可以打印出来查看他的值
args.push(newParam);
console.log(newParam);
return compose(...args);
} else if (arity == 1) {
// 将操作目标放在最后一个参数,目标可能是一个函数,也可能是一个值,因此针对不同的情况做不同的处理
if (!tag) {
return args[0].bind(null, args[1]);
} else {
return args[0].call(null, args[1]);
}
}
}
OK,我们来验证一下封装的这个compose函数是否可靠。
var fn1 = function(a) { return a + 100 }
var fn2 = function(a) { return a + 10 }
var fn3 = function(a) { return a + 20 }
var bar = compose(fn1, fn2, fn3, 10);
console.log(bar());
// 输出结果
// 30
// 40
// 140
var base = function() {
return arguments[0] + arguments[1];
}
var foo1 = function(fn) {
return fn.bind(null, 20);
}
var foo2 = function(fn) {
return fn.bind(null, 30);
}
var res = compose(foo1, foo2, base);
console.log(res());
// 输出结果
// f() {}
// 50
通过这两个验证的例子,我们确定封装的这个组合函数还是比较可靠的。因此就可以直接放心的使用了。
当然,组合函数还可以借助柯里化封装得更加灵活。
window.renderIndex = compose(withLogin, withEnvironment, renderIndex);
// 还可以这样
window.renderIndex = compose(withLogin, withEnvironment)(renderIndex);
这里不再继续深入探讨具体的封装方法,我们在使用时可以借助工具库lodash.js中的flowRight来实现这种灵活的效果。
// ES6 模块化语法,引入flowRight函数
import flowRight from 'lodash/flowRight';
// ...
// ES6模块化语法 对外暴露接口
export default flowRight(withLogin, withEnvironment)(renderIndex);