Blog - wingsico

1
const compose = (...fns) => (arg) => fns.reduce((composed, f) => f(composed), arg)

预备知识

在学习react过程中碰到这样的一段代码,勉强能知道这是一段函数式的代码,并且配合es6的箭头函数以及扩展运算符组合而成的,但最初看到这段代码的时候还是把我难住了,主要问题有三个:

  1. 两个箭头函数写法
  2. 函数作为参数传递
  3. Array.prototype.reduce 用法

() => () => console.log(1)

这个不难理解,箭头函数可以省略大括号,则箭头后跟着的就是其返回值,在这里他的返回值就是一个函数,这里也就是一个函数式编程的思想,函数作为一等公民,和其他类型的值用法可以一致。用es5重写一下:

1
2
3
4
5
6
7
var test = function() {
return function() {
console.log(1)
}
}
test() // 返回: function () {console.log(1)}
test()() // 1

(fn) => fn(‘我被参数执行了’)

这个也比较好理解,根据第一点,函数可以作为返回值,也能作为参数,用一个简单的例子表示:

1
2
3
4
5
let fn = (text) => console.log(text)
(function(fn){
let text = '我被参数执行了'
fn(text)
})() // 打印出: 我被参数执行了

Array.prototype.reduce

至于reduce,之前对他的了解只有是累加器,把数组前面的值与目前的值进行运算,并作为一个值与数组的下一项的值进行运算,是从这样一个例子得到的认知:

1
[1, 2, 3].reduce((pre, cur) => pre + cur, 0) // 6

对reduce有一个模糊的认识,且不清楚他拥有的参数及其含义。查阅相关资料后,了解了: reduce 共有两个参数,第一个参数为callback,即回调函数,用于对数组内的值进行操作,且必须有返回值;第二个参数为initialValue,是一个可选的参数,即初始值,即第一个pre的值初始化,如果没有设置这个参数,则pre默认为第一个数组的值,需要注意的是,初始值的类型决定了最后得到的结果的类型,这两者是保持一致的

拿上面的那个例子来说:

1
2
3
4
5
6
7
8
9
10
11
[1, 2, 3].reduce((pre, cur) => pre + cur, 0) // 6

// 实际执行过程
/*
1. pre === 0, cur === 1
2. pre === 0 + 1, cur === 2
3. pre === 0 + 1 + 2, cur === 3
4. pre === 0 + 1 + 2 + 3, cur === ""
*/
// 比数组的长度多执行一次
// 若没有设置初始值,则和数组的长度的执行次数一致

这个也很好理解,将前面return的值给下一次使用,但是,为了理解我们最开始给出的那段代码,这个值就是一个问题,这个也同样是和第一点和第二点一样, 甚至是第一点和第二点的结合,函数式编程思想,函数作为参数传递进来,又作为返回值给下一次使用。 这里只用到第二点,函数作为参数传递。 至此,解决了三个问题,为了读懂这一段代码,需要把这三个知识串在一起使用。

理解

首先,要理解第一个参数fns,从参数的命名上看,我们可以知道fns应该表示成数组,然后需要注意的是:传递的参数不应该以数组的形式传递(这也是我一开始遇到的问题,并且困扰到最后,也是对扩展运算符的了解不够或者误解),而应该以多个参数的形式传递。具体详情查看es6 数组的扩展 再者是连续的两个双箭头,() => ... => ...意味着他是一个会返回函数的函数,第一次调用的结果:

1
(arg) => fns.reduce((composed, f) => f(composed), arg)

通过对reduce的理解,composed为数组的前一个return的值(在arg缺省的情况下,数组的第一项作为composed),cur为当前的值。

注意这里对的定义,对于reduce来说,值可以是任何一种类型(当没有初始值限制的时候),当然也包括函数,对于我们这一段代码来说,当初始值缺省时,第一项的返回值可以不为函数,最后一项的返回值也可以不为函数,也可以没有返回值,但中间部分的返回值必须为一个函数,由f(composed)可以了解到这一点;

当初始值存在时,则从第一项到倒数第二项的参数的返回值必须为函数(因为第一项需要接受初始值作为参数的传递)。

之前还是没看懂,现在终于了解了,对于这个代码,每一个参数必须为函数,且均要有参数(如果没有给arg赋值的话,第一个可以没有参数),这个函数的返回值与参数 arg 有关,arg是一直存在的,之前没搞懂这一点,就算调用时不赋值,也为 undefined,因此第一个 pre 永远为 undefined(除非你给arg赋值),且除了最后一个函数外,均必须要有返回值,若最后一个函数没有返回值,则整体就没有返回值。 由此,可以得到这个compose函数的用法

1
2
3
4
5
6
7
8
let first = () => 1
let second = (a) => a + 1
let last = (b) => console.log(b)
let result = compose(first, second, last)() // 打印出 2,不是返回值为2
console.log(result) // undefined
last = (b) => b + 1
result = compose(first, second, last)() // 返回值为3
console.log(result) // 3

相当于

1
last(second(first())) // 返回值为2

小结

至此,终于搞明白了compose函数的作用,就是为了以一个简明的方式实现函数的嵌套,即组合嵌套函数,有多个参数,实参为函数的形式存在,其返回值也为函数,即组合之后的函数。

 评论