compose函数
先po一下实现方法
// 解法1 迭代
function compose(fns) {
let fnsArr = [...fns]; // 因为arguments是个类数组,所以需要先将它转化为数组,方便后续迭代操作,类数组转化为数组的方法还有很多,比如slice(0),concat(),Array.from()等
let index = fnsArr.length - 1; // 获取最后一个函数的索引
let res = null; // 初始化结果,
return function fn() { // 利用高阶函数进行函数的迭代调用
let args = [...arguments]; // 子函数的入参
res = fnsArr[index].apply(this, args); // 将当前函数的执行结果保存在res中,作为下一个函数的入参使用
if (index <= 0) {
return res; // 循环结束
}
index--;
return fn.call(null, res);// 递归调用
};
}
// 迭代的过程可以用reduce替换
// 解法2-reduce
const compose=(...funcs)=> {
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((pre, curr) => (...arg) => pre(curr(...arg)));
}
// 从右向左,reduceRight可以完美的解决这个问题
// 解法3——更加精炼的reduceRight
const compose=(...args)=>(...x) => args.reduceRight((pre, curr) => curr(x));
只需要获取函数实现方式的同学现在可以出门右转啦,想看compose函数详解的继续往下
刚开始知道这个函数是在看webpack,介绍loader的时候有一句话:loader 从右到左(或从下到上)地取值(evaluate)/执行(execute),按照一般的场景,函数的执行顺序不都是自上而下,自左向右的么,于是就发现了compose这个神奇的函数。
compose函数是函数式编程中使用较多的一种写法,顾名思义,它可以将一堆函数组合在一起使用,将外部函数依次通过内部函数的加工,最后输出结果。
这个解释可能还是有点迷惑,那就拿loader来说明一下,我们在进行webpack配置的时候,遇到css预编译工具(sass,less等),需要自上而下的配置style-loader,css-loader以及sass-loader/less-loader,如果打乱顺序,就会出现一些错误。webpack的解释如下:
loader 支持链式调用。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
// [style-loader](/loaders/style-loader)
{ loader: 'style-loader' },
// [css-loader](/loaders/css-loader)
{
loader: 'css-loader',
options: {
modules: true
}
},
// [sass-loader](/loaders/sass-loader)
{ loader: 'sass-loader' }
]
}
]
}
};
通俗的解释就是当遇到sass文件时,首先需要通过sass-loader将sass文件编译成css,然后再通过css-loader将文件中的@import
和 url()
进行处理,最后再通过style-loader将处理好的CSS插入到DOM中。
在这个步骤中,每一个环节的入参都是上一个环节的处理结果,如果把它精简成函数,那应该就是这样的
// 举个🌰
function a(){
// TODO
}
function b(){
// TODO
}
function c(){
// TODO
}
// 其中函数a的入参要依赖于函数b的结果,函数b的入参又要依赖于函数c的结果
const demo = a(b(c()))
console.log(demo)
问题来了,如果环节数很多很多,那可能会出现这种情况a(b(c(d(e(f(g()))))))
,这种代码看上去就头皮发麻,更别说要是哪位在开发的时候手抖删了那么一个右括号,找问题都可能找半天,于是人们开始思考,能不能以一种更优雅的形式对它进行处理?于是就出现了常见的compose函数的格式
function compose(){
// TODO
}
function a(){
// TODO
}
function b(){
// TODO
}
function c(){
// TODO
}
let demo = compose(a,b,c)()
console.log(demo)
这样看起来是不是就顺眼多了,不管千层饼里再夹千层饼,我始终都在大气层,接着问题出现了。。。这个函数调用确实很方便,但是要怎么实现呢?这里把开头的实现方法再po出来,是不是会容易懂一点了
// 解法1 迭代
function compose(fns) {
let fnsArr = [...fns]; // 因为arguments是个类数组,所以需要先将它转化为数组,方便后续迭代操作,类数组转化为数组的方法还有很多,比如slice(0),concat(),Array.from()等
let index = fnsArr.length - 1; // 获取最后一个函数的索引
let res = null; // 初始化结果,
return function fn() { // 利用高阶函数进行函数的迭代调用
let args = [...arguments]; // 子函数的入参
res = fnsArr[index].apply(this, args); // 将当前函数的执行结果保存在res中,作为下一个函数的入参使用
if (index <= 0) {
return res; // 循环结束
}
index--;
return fn.call(null, res);// 递归调用
};
}
// 迭代的过程可以用reduce替换
// 解法2-reduce
const compose=(...funcs)=> {
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((pre, curr) => (...arg) => pre(curr(...arg)));
}
// 从右向左,reduceRight可以完美的解决这个问题
// 解法3——更加精炼的reduceRight
const compose=(...args)=> (...x) => args.reduceRight((pre, curr) => curr(x));
compose函数在很多中间件中都会使用,比如之前说的loader,在redux中也有暴露compose方法