查看以下解决相同问题的代码段,并考虑最喜欢哪一个。
这是第一个: | 这是第二个: |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(int => isEven(int)) .filter(int => isBiggerThan(3, int)) .map(int => int + 1) .map(int => toChar(int)) .filter(char => !isVowel(char)) .join('') // 'fhjl'
| [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('') |
“我敢打赌第二个选项比第一个选项具有更好的可读性,”该材料的作者说,我们今天出版了该版本的翻译。 据他介绍,重点在于
filter()
和
map()
方法的参数。

今天,我们将讨论与第一个示例类似的如何回收代码,以使其看起来像第二个示例中的代码。 本文的作者承诺,在您理解了它的工作原理之后,您将以一种新的方式与您的程序相关联,并且您将无法忽略过去看起来很正常且不需要进行改进的内容。
功能简单
考虑一个简单的
sum()
函数,该函数将传递给它的数字相加:
const sum = (a, b) => a + b sum(1, 2)
我们重写它,为新函数命名为
csum()
:
const csum = a => b => a + b csum(1)(2)
其新版本的工作方式与原始版本完全相同,唯一的区别在于此新函数的调用方式。 即,
sum()
函数一次获取两个参数,而
csum()
一次获取一个相同的参数。 实际上,当调用
csum()
,将调用两个函数。 特别是,考虑调用
csum()
并将数字1传递给它的情况:
csum(1)
对
csum()
这种调用导致以下事实:它返回一个函数,该函数可以采用在其通常的调用期间传递给
csum()
的第二个数字参数,并返回在此参数上加一个的结果。 调用此函数
plusOne()
:
const plusOne = csum(1) plusOne(2)
处理数组
在JavaScript中,您可以使用多种特殊方法来处理数组。 假设
map()
方法用于将传递给它的函数应用于数组的每个元素。
例如,为了使整数数组的每个元素增加1(更确切地说,要形成一个包含原始数组的元素增加1的新数组),可以使用以下构造:
[1, 2, 3].map(x => x + 1)
换句话说,发生的情况可以描述如下:函数
x => x + 1
接受一个整数,并以一系列整数返回其后的数字。 使用上面讨论的
plusOne()
函数,可以将该示例重写如下:
[1, 2, 3].map(x => plusOne(x))
在这里值得放慢脚步,思考正在发生的事情。 如果这样做,可以看到在考虑的情况下,
x => plusOne(x)
和
plusOne
(请注意,在这种情况下,函数名称后没有方括号)是等效的。 为了更好地理解这一点,请考虑
otherPlusOne()
函数:
const otherPlusOne = x => plusOne(x) otherPlusOne(1)
该函数的结果将与通过简单调用我们已知的
plusOne()
获得的结果相同:
plusOne(1)
出于同样的原因,我们可以讨论以下两种构造的等效性。 这是我们已经看到的第一个:
[1, 2, 3].map(x => plusOne(x))
这是第二个:
[1, 2, 3].map(plusOne)
另外,请记住
plusOne()
函数是如何创建的:
const plusOne = csum(1)
这使我们可以使用
map()
重写我们的构造,如下所示:
[1, 2, 3].map(csum(1))
现在,使用相同的技术,
isBiggerThan()
函数。 如果需要,请尝试自己做,然后继续阅读。 在使用
filter()
方法时,这将消除不必要的构造。 首先,让我们将代码转换为这种形式:
const isBiggerThan = (threshold, int) => int > threshold [1, 2, 3, 4].filter(int => isBiggerThan(3, int))
然后,除去所有多余的内容,我们获得了您在本材料开始时已经看到的代码:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('')
现在,我们考虑两个简单的规则,这些规则允许您以此处讨论的样式编写代码。
规则编号1
以下两个构造是等效的:
[…].map(x => fnc(x)) […].map(fnc)
规则编号2
始终可以重写回调以减少用于调用它的参数数量:
const fnc = (x, y, z) => … […].map(x => fnc(x, y, z)) const fnc = (y, z) => x => … […].map(fnc(y, z))
如果您自己编写了
isBiggerThan()
函数,那么您可能已经采取了这种转换方法。 假设我们需要大于3的数字才能通过过滤器,可以这样做:
const isBiggerThan = (threshold, int) => int > threshold […].filter(int => isBiggerThan(3, int))
现在,我们重写
isBiggerThan()
函数,以便可以在
filter()
方法中使用它,而不使用
int=>
构造:
const isBiggerThan = threshold => int => int > threshold […].map(isBiggerThan(3))
锻炼身体
假设我们有以下代码片段:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 keepGreatestChar('b', 'f') // 'f' // 'f' 'b'
现在,基于
keepGreatestChar()
函数,创建
keepGreatestCharBetweenBAnd()
函数。 我们需要通过调用它,只将一个参数传递给它,同时它将传递给它的字符与字符
b
。 该功能可能如下所示:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 const keepGreatestCharBetweenBAnd = char => keepGreatestChar('b', char) keepGreatestCharBetweenBAnd('a')
现在编写
greatestCharInArray()
函数,该函数在
reduce()
数组方法中使用
keepGreatestChar()
函数,可让您搜索“最大”字符,不需要参数。 让我们从下面的代码开始:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 const greatestCharInArray = array => array.reduce((acc, char) => acc > char ? acc : char, 'a') greatestCharInArray(['a', 'b', 'c', 'd'])
要解决此问题,请实现
creduce()
函数,该函数可以在
creduce()
函数中使用,该函数在实际应用中允许不向其中传递任何东西,除非要在其中查找具有最大代码的字符的数组。
creduce()
函数必须足够通用,以便可以解决任何需要使用标准
reduce()
数组方法功能的问题。 换句话说,该函数必须采用回调,初始值和要使用的数组。 因此,您应该获得一个可以使用以下代码片段的函数:
const greatestCharInArray = creduce(keepGreatestChar, 'a') greatestCharInArray(['a', 'b', 'c', 'd'])
总结
也许现在您有一个问题,为什么按照此处介绍的方法处理的方法的名称以字符
c
开头。
c
字符是curried的首字母缩写,我们讨论了curried函数如何帮助提高代码的可读性。 应当指出,这里我们并未努力严格遵守函数式编程的原则,但是,我们相信此处讨论的内容的实际应用可以使我们改进代码。 如果您对JavaScript的泛滥这个话题很感兴趣-建议您阅读本书第4章有关函数式编程的知识,并且,由于您已经达到了这一点,通常,请阅读整本书。 另外,如果您不熟悉功能编程,请查阅
该入门资料。
亲爱的读者们! 您是否在JavaScript开发中使用函数currying?
