JavaScript中的函数式编程--函数组合
2020/7/2 11:55:35
本文主要是介绍JavaScript中的函数式编程--函数组合,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
函数组合
问题:纯函数和柯里化很容易写出洋葱(一层套一层)代码,形如:h(g(f(x))),实际中遇到的问题如:获取数组的最后一个元素在转换成大写字母,_.toUpper(_.first(_.reverse(array)))
解决:函数组合可以让我们把细粒度的函数重新组合生成一个新的函数
管道
下面这张图标识程序中使用函数处理数据的过程,给fn函数输入参数a,返回结果b,可以想象a通过一个管道得到了b数据。
思考:如果中间的管道(fn)特别长(复杂)时我们可不可以将其拆分成多个短管道(小函数)? 下面这张图中可以想象成把fn这个管道拆分成了3个管道f1、f2、f3,数据a通过管道f3得到结果m,m在通过管道f2得到结果n,n在通过管道f1得到最终结果b 使用函数展示:fn = compose(f1, f2, f3) b = fn(a) 复制代码
函数组合的概念
函数组合(compose):如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
- 函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终结果
- 函数组合默认是从右到左执行
函数组合示例:
function compose(f, g) { // 组合完成后返回一个函数,此函数接收一个参数 return function(value) { // 依次从右向左执行函数 return f(g(value)) } } // 使用:获取数组中的最后一个元素(使用反转+取第一个元素) function reverse(array) { return array.reverse() } function first(array) { return array[0] } // 使用compose const last = compose(first, reverse) console.log(last([1, 2, 3, 4, 5 ,6])) 复制代码
Lodash中的组合函数
- lodash中的组合函数
- lodash中的组合函数flow()或者flowRight(),他们都可以组合多个函数
- flow()是从左到右运行
- flowRight()是从右到左运行,使用的更多一些
lodash示例:
const _ = require('lodash') const reverse = arr => arr.reverse() const first = arr => arr[0] const toUpper = s => s.toUpperCase() const f = _.flowRight(toUpper, first, reverse) console.log(f['one', 'tow', 'three']) 复制代码
组合函数的实现原理
分析:调用组合函数会一次执行传入的函数,每次执行完成一个函数后会将结果交给下一个要执行的函数
// 传入参数个数不确定,所以使用ES6中展开剩余参数(...args) function compose(...args) { // 执行后返回一个函数 return function (value) { // 需要接收一个参数 // 执行完成后需要返回结果 // 实现传入函数的从右往左执行,使用reverse将args反转 // 接下来需要执行args中的函数并将结果给后续使用,reduce正好满足需求 return args.reverse().reduce((acc, fn) => fn(acc), value) } } // ES6简化 const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value) const reverse = arr => arr.reverse() const first = arr => arr[0] const toUpper = s => s.toUpperCase() const f = compose(toUpper, first, reverse) console.log(f(['one', 'tow', 'three'])) 复制代码
函数组合-结合律
函数的组合要满足结合律(associativity)
- 我们既可以把g和h组合,还可以把f和g组合,结果都是一样的
示例:
// 结合律 let f = compose(f, g, h) let associative = compose(compose(f, g), h) == compose(f, compose(g, h)) // => true 复制代码
函数组合-调试
如何调试组合函数?
- 首先组合的函数都是纯函数,之前我们说过纯函数有点重有一条是便于调试,那我们便可以使用这一个特点
示例:按照指定格式转换字符串
// 输入字符 'ABC DE FGHI' // 输出字符 'abc-de-fghi' // 分析:1.首先需要将字符按照空格分隔:split // 分析:2.将字符转成小写:toLower // 分析:3.用-分隔字符:join const _ = require('lodash') // split(str, sep) 使用柯里化将其转化为一元参数 const split = _.curry((sep, str) => _.split(str, sep)) // toLower(str) 单参数纯函数,不需要处理,直接使用 _.toLower 即可 // join(array, sep) const join = _.curry((sep, array) => _.join(array, sep)) // 当我们使用split分割以后得到一个数组,此时不能直接使用toLower // 此时我们遍历数组再使用toLower处理,需要一个map函数 const map = _.curry((fn, array) => _.map(array, fn)) // 进行函数组合 const f = _.flowRight(join('-'), map(_.toLower), split(' ')) // 以上是我们为调试做的准备,那么如果我们想在执行组合中某个函数后得到结果该怎么办 // 首先我们函数组合中每执行完一个函数都会将结果返回 // 那我们根据这个特点是不是可以在该函数后插入一个log函数呢? const log = v => { console.log(v) // 需要将结果返回供之后函数使用 return v } const f = _.flowRight(join('-'), log, map(_.toLower), log, split(' ')) // 此时log可输出,但是我们无法清除区分哪个输出对应哪个log,改造下 const trace = _.curry((tag, v) => { console.log(tag, v) return v }) const f = _.flowRight(join('-'), trace('map之后'), map(_.toLower), trace('split之后'), split(' ')) console.log(f('ABC DE FGHI')) 复制代码
Lodash中的FP(Function Programming)模块
lodash/fp
- lodash的fp模块提供了实用的对函数式编程友好的方法
- 提供了不可变**auto-curried(已被柯里化) iterates-first(函数优先) data-last(数据滞后)**的方法
示例:lodash中的普通模块与fp模块
// 普通模块 // 未被柯里化的函数:数据优先,函数滞后 const _ = require('lodash') _.map(['a', 'b', 'c'], _.toUpper) // => ['A', 'B', 'C'] _.map(['a', 'b', 'c']) // => ['a', 'b', 'c'] _.split('Hello World', ' ') // => ['Hello', 'World'] // lodash/fp模块 // 柯里化的函数:函数优先,数据滞后 cnost fp = require('lodash/fp') // 以下两种结果相同 fp.map(fp.toUpper, ['a', 'b', 'c']) fp.map(fp.toUpper)(['a', 'b', 'c']) fp.split(' ', 'Hello World') fp.split(' ')('Hello World') 复制代码
示例:使用fp模块重写调试中的例子
const fp = require('lodash/fp') const f = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' ')) console.log(f('ABC DE FGHI')) 复制代码
Point Free
Point Free:我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的哪个参数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数
- 不需要指明处理的数据
- 只需要合成运算过程
- 需要定义一些辅助的基本运算函数
解释示例:组合函数
// 1.没有指明处理的数据 // 2.通过组合函数组合多个运算函数 // 3.定义了join、map、toLower、split运算函数 const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' ')) 复制代码
示例:字符串转换
// 将空格替换为_,大写转换成小写 // Hello World => hello_world const fp = require('lodash/fp') // 分析:手写大写转小写,然后正则匹配_替换空格 const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower) console.log(f('Hello World')) 复制代码
通过以上示例我们可以看出Point Free其实就是函数组合
Point Free案例
把一个字符串中的首字母提取并转换成大写,使用. 作为分隔符得到新的字符串
// 输入: world wide web // 输出:W. W. W // 分析:1.将字符串按照空格分割 // 分析:2.遍历数组将字母全部转换为大写 // 分析:3.遍历数组取转换后单次的首字母 // 分析:4.使用. 分割获取新字符串 const fp = require('lodash/fp') const f = fp.flowRight(fp.join('. '), fp.map(fp.first), fp.map(fp.toUpper), fp.split(' ')) console.log(f('world wide web')) // 通过以上方式我们实现了要求 // 但是我们发现组合中使用了两次map遍历,对性能有一定影像,来优化一下 // 两次map遍历都是对同一个数组操作,所以可以合并一下 const f = fp.flowRight(fp.join('. '), fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(' ')) 复制代码
这篇关于JavaScript中的函数式编程--函数组合的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-30Sentinel限流教程:新手入门指南
- 2024-12-30Springboot框架教程:新手入门及初级技巧
- 2024-12-30Springboot框架教程:初学者必看指南
- 2024-12-30Springboot企业级开发教程:从入门到实践
- 2024-12-30Springboot企业级开发教程:新手入门与实践
- 2024-12-30SpringBoot微服务教程:入门与实践
- 2024-12-30SpringBoot项目开发教程:从入门到实践
- 2024-12-30Springboot项目开发教程:从入门到实践
- 2024-12-30SpringCloud Alibaba教程:轻松入门与实践
- 2024-12-30SpringCloud Alibaba教程:入门与实践指南