javascript的函数组合与柯里化的详解(附示例)
本篇文章给大家带来的内容是关于javascript的函数组合与柯里化的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
我们都知道单一职责原则,其实面向对象的SOLID中的S(SRP, Single responsibility principle)。在函数式当中每一个函数就是一个单元,同样应该只做一件事。但是现实世界总是复杂的,当把现实世界映射到编程时,单一的函数就没有太大的意义。这个时候就需要函数组合和柯里化了。
链式调用
如果用过jQuery的都晓得啥是链式调用,比如$('.post').eq(1).attr('data-test', 'test')
.javascript原生的一些字符串和数组的方法也能写出链式调用的风格:
'Hello, world!'.split('').reverse().join('') // "!dlrow ,olleH"
首先链式调用是基于对象的,上面的一个一个方法split
, reverse
, join
如果脱离的前面的对象"Hello, world!"是玩不起来的。
而在函数式编程中方法是独立于数据的,我们可以把上面以函数式的方式在写一遍:
const split = (tag, xs) => xs.split(tag) const reverse = xs => xs.reverse() const join = (tag, xs) => xs.join(tag) join('',reverse(split('','Hello, world!'))) // "!dlrow ,olleH"
你肯定会说,你是在逗我。这比链式调用好在哪儿了?这里还是依赖于数据的啊,没有传递`'Hello, world!',你这一串一串的函数组合也转不起来啊。这里唯一的好处也就是那几个单独的方法可以复用了。莫慌,后面还有那么多内容我怎么也会给你优化(忽悠)好的。再进行改造前,我们先介绍两个概念,部分应用和柯里化。
部分应用
部分应用是一种处理函数参数的流程,他会接收部分参数,然后返回一个函数接收更少的参数。这个就是部分应用。我们用bind
来实现一把:
const addThreeArg = (x, y, z) => x + y + z; const addTwoArg = addThreeNumber.bind(null, 1) const addOneArg = addThreeNumber.bind(null, 1, 2) addTwoArg(2, 3) // 6 addOneArg(7) // 10
上面利用bind
生成了另外两个函数,分别接受剩下的参数,这就是部分应用。当然你也可以通过其他方式实现。
部分应用存在的问题
部分应用主要的问题在于,它返回的函数类型无法直接推断。正如前面所说,部分应用返回一个函数接收更少的参数,而没有规定返回的参数具体是多少个。这也就是一些隐式的东西,你需要去查看代码。才知道返回的函数接收多少个参数。
柯里化
柯里化定义:你可以调一个函数,但是不一次将所有参数传给它。这个函数会返回一个函数去接收下一个参数。
const add = x => y => x + y const plusOne = add(1) plusOne(10) // 11
柯里化的函数返回一个只接收一个参数的函数,返回的函数类型可以预测。
当然在实际开发中,有很多的函数都不是柯里化的,我们可以使用一些工具函数来转化:
const curry = (fn) => { // fn可以是任何参数的函数 const arity = fn.length; return function $curry(...args) { if (args.length < arity) { return $curry.bind(null, ...args); } return fn.call(null, ...args); }; };
也可以用开源库Ramda里提供的curry方法。
哦,柯里化。有什么用呢?
举个例子
const currySplit = curry((tag, xs) => xs.split(tag)) const split = (tag, xs) => xs.split(tag) // 我现在需要一个函数去split "," const splitComma = currySplit(',') //by curry const splitComma = string => split(',', string)
可以看到柯里化的函数生成新函数时,和数据完全没有关系。对比两个生成新函数的过程,没有柯里化的相对而言就有一点啰嗦了。
函数组合
先给代码:
const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0];
其实compose做的事情一共两件:
接收一组函数,返回一个函数,不立即执行函数
组合函数,将传递给他的函数从左到右组合。
可能有同学对上面的reduceRight不是很熟悉,我给个2元和3元的例子:
const compose = (f, g) => (...args) => f(g(...args)) const compose3 = (f, g, z) => (...args) => f(g(z(...args)))
函数调用是从左到右,数据流也是一样的从左到右。当然你可以定义从右到左的,不过从语义上来说就不那么表意了。
好,现在让我们来优化一下最开始的例子:
const split = curry((tag, xs) => xs.split(tag)) const reverse = xs => xs.reverse() const join = curry((tag, xs) => xs.join(tag)) const reverseWords = compose(join(''), reverse, split('')) reverseWords('Hello,world!');
是不是简洁易于理解多了。这里的reverseWords
也是我们之前讲过的Pointfree的代码风格。不依赖数据和外部状态,就是组合在一起的一个函数。
Pointfree我在上一篇介绍过JS函数式编程 - 概念,也阐述了其优缺点,有兴趣的小伙伴可以看看。
函数组合的结合律
先回顾一下小学知识加法结合律:a+(b+c)=(a+b)+c
。我就不解释了,你们应该能理解。
回过来看函数组合其实也存在结合律的:
compose(f, compose(g, h)) === compose(compose(f, g), h);
这个对于我们编程有一个好处,我们的函数组合可以随意组合并且缓存:
const split = curry((tag, xs) => xs.split(tag)) const reverse = xs => xs.reverse() const join = curry((tag, xs) => xs.join(tag)) const getReverseArray = compose(reverse, split('')) const reverseWords = compose(join(''), getReverseArray) reverseWords('Hello,world!');
脑图补充:
以上是javascript的函数组合与柯里化的详解(附示例)的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

C++lambda表达式为函数式编程带来了优势,包括:简洁性:匿名内联函数,提升代码可读性。代码重用:可传递或存储lambda表达式,方便重用代码。封装:提供封装代码段的方法,无需创建单独函数。实战案例:过滤列表中的奇数。计算列表中元素的总和。lambda表达式实现了函数式编程的简洁性、可重用性和封装性。

通过使用惰性数据结构,可以在Go语言中实现惰惰求值:创建一个包装器类型,封装实际值,仅在需要时才计算。在函数式程序中优化斐波那契数列的计算,推迟中间值的计算,直到实际需要。这可以消除不必要的开销,提高函数式程序的性能。

在Go中使用函数式编程时需要注意五个常见错误和陷阱:避免引用意外修改,确保返回新创建的变量。解决并发性问题,使用同步机制或避免捕获外部可变状态。谨慎使用偏函数化,以提高代码可读性和可维护性。始终处理函数中的错误,确保应用程序的健壮性。考虑性能影响,使用内联函数、扁平化数据结构和操作批处理来优化代码。

pythonLambda表达式是一个强大且灵活的工具,可用于创建简洁、可读且易于使用的代码。它们非常适合快速创建匿名函数,这些函数可以作为参数传递给其他函数或存储在变量中。Lambda表达式的基本语法如下:lambdaarguments:expression例如,以下Lambda表达式将两个数字相加:lambdax,y:x+y这个Lambda表达式可以传递给另一个函数作为参数,如下所示:defsum(x,y):returnx+yresult=sum(lambdax,y:x+y,1,2)在这个例子

JS-Torch简介JS-Torch是一种深度学习JavaScript库,其语法与PyTorch非常相似。它包含一个功能齐全的张量对象(可与跟踪梯度),深度学习层和函数,以及一个自动微分引擎。JS-Torch适用于在JavaScript中进行深度学习研究,并提供了许多方便的工具和函数来加速深度学习开发。图片PyTorch是一个开源的深度学习框架,由Meta的研究团队开发和维护。它提供了丰富的工具和库,用于构建和训练神经网络模型。PyTorch的设计理念是简单和灵活,易于使用,它的动态计算图特性使

python中的Lambda表达式是匿名函数的另一种语法形式。它是一个小型匿名函数,可以在程序中任何地方定义。Lambda表达式由一个参数列表和一个表达式组成,表达式可以是任何有效的Python表达式。Lambda表达式的语法如下:lambdaargument_list:expression例如,下面的Lambda表达式返回两个数字的和:lambdax,y:x+y这个Lambda表达式可以传递给其他函数,例如map()函数:numbers=[1,2,3,4,5]result=map(lambda

Go和Node.js在类型化(强/弱)、并发(goroutine/事件循环)、垃圾收集(自动/手动)上存在差异。Go具备高吞吐量、低延迟,适用于高负载后端;Node.js擅长异步I/O,适合高并发、短请求。两者的实战案例包括Kubernetes(Go)、数据库连接(Node.js)、Web应用程序(Go/Node.js)。最终选择取决于应用程序需求、团队技能和个人偏好。
