概念

如果一个值需要经过多个函数运算才能得到最终的值,那么可以把中间过程的函数合并成一个函数。函数组合默认的执行顺序是从右到左。函数组合要满足数学中的结合律。

  1. // 函数组合演示
  2. function compose (f1, f2) {
  3. return function (value) {
  4. return f1(f2(value))
  5. }
  6. }
  7. function reverse (array) {
  8. return array.reverse()
  9. }
  10. function first (array) {
  11. return array[0]
  12. }
  13. const last = compose(first, reverse)
  14. console.log(last([1, 2, 3, 4]))

模拟实现lodash中的组合函数flowRight

flowRight可以把多个函数组合成一个新函数

  1. // lodash 中的函数组合的方法 _.flowRight()
  2. const _ = require('lodash')
  3. const reverse = arr => arr.reverse()
  4. const first = arr => arr[0]
  5. const toUpper = s => s.toUpperCase()
  6. const f = _.flowRight(toUpper, first, reverse)
  7. console.log(f(['one', 'two', 'three']))
  1. const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value)

由于传入的函数个数不固定,所以使用剩余参数…args的形式。
flowRight函数的执行顺序是从右到左,所以先使用reverse反转函数数组。
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。reduce() 可以作为一个高阶函数,用于函数的 compose。

  1. array.reduce(function(total, currentValue, currentIndex?, arr?), initialValue)

函数组合的调试

  1. // 函数组合 调试
  2. // NEVER SAY DIE --> never-say-die
  3. const _ = require('lodash')
  4. // const log = v => {
  5. // console.log(v)
  6. // return v
  7. // }
  8. const trace = _.curry((tag, v) => {
  9. console.log(tag, v)
  10. return v
  11. })
  12. //trace是一个常量而不是函数,所以无法提升到全局,需要写到调用者的前面
  13. // _.split() lodash的split方法需要传入两个参数,而且参数位置不满足我们的需求,所以使用curry方法创造一个柯里化的split方法,并且颠倒参数位置。join和map方法相似。
  14. const split = _.curry((sep, str) => _.split(str, sep))
  15. // _.toLower()
  16. const join = _.curry((sep, array) => _.join(array, sep))
  17. const map = _.curry((fn, array) => _.map(array, fn))
  18. const f = _.flowRight(join('-'), trace('map 之后'), map(_.toLower), trace('split 之后'), split(' '))
  19. console.log(f('NEVER SAY DIE'))

从上面的代码中可以看到,lodash模块中提供的一些方法是没有经过柯里化的,对函数式编程并不友好。所以lodash中提供了一个fp模块,fp模块中的方法都是经过柯里化的。传递参数是函数优先,数据在后的,即在传参数的时候都是优先传入函数的(第一个参数都是函数)。

  1. // lodash 和 lodash/fp 模块中 map 方法的区别
  2. const _ = require('lodash')
  3. console.log(_.map(['23', '8', '10'], parseInt))
  4. //parseInt('23', 0, array)
  5. const fp = require('lodash/fp')
  6. console.log(fp.map(parseInt, ['23', '8', '10']))

lodash的map方法,函数参数需要传入3个参数,而且函数参数是后传入的。而fp模块中的map方法只需传入两个参数,而且优先传入函数参数,函数参数只需要接收一个参数。

Point Free

  1. 我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参

数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数。Point Free模式实际上就是函数组合。

  1. // 把一个字符串中的首字母提取并转换成大写, 使用. 作为分隔符
  2. // world wild web ==> W. W. W
  3. const fp = require('lodash/fp')
  4. // const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.first), fp.map(fp.toUpper), fp.split(' '))
  5. const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(' '))
  6. console.log(firstLetterToUpper('world wild web'))