代码组合

在学习代码组合之前,我们需要回顾一下高阶函数的应用。

在高阶函数的章节学习中,我们探讨了一个实践中的案例。每一个页面都会判断用户的登录状态,因此我们封装了一个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);

results matching ""

    No results matching ""