功能组成:可维护代码的构建块

Jeff MottDan PrinceSebastian Seitz对本文进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!

导体在发光屏幕前的图像

以功能性方式考虑JavaScript的优势之一是能够使用小型,易于理解的单个功能来构建复杂的功能。 但是有时这涉及向后而不是向前看问题,以便弄清楚如何创建最优雅的解决方案。

在本文中,我将采用逐步的方法来检查JavaScript中的功能组合,并演示它如何产生易于推理且错误较少的代码。

嵌套功能

组合是一种技术,它允许您采用两个或多个简单功能,并将它们组合为一个更复杂的功能,该功能对输入的任何数据按逻辑顺序执行每个子功能。

要获得此结果,请将一个函数嵌套在另一个函数中,然后对内部函数的结果重复执行外部函数的操作,直到产生结果为止。 并且结果可能会有所不同,具体取决于功能的应用顺序。

使用我们已经熟悉的JavaScript编程技术,可以通过将函数调用作为参数传递给另一个函数来轻松地证明这一点:

function addOne(x) {
  return x + 1;
}
function timesTwo(x) {
  return x * 2;
}
console.log(addOne(timesTwo(3))); //7
console.log(timesTwo(addOne(3))); //8

在这种情况下,我们定义了一个函数addOne()将一个值添加到一个值,以及一个timesTwo()函数,将一个值乘以2。 通过将一个函数的结果作为另一个函数的参数传递,我们可以看到,即使使用相同的初始值,将其中一个嵌套在另一个函数中也会如何产生不同的结果。 首先执行内部函数,然后将结果传递给外部函数。

命令式组成

如果要重复执行相同的操作序列,可以定义一个新功能,该功能先自动应用另一个较小的功能,然后再应用另一个更方便。 可能看起来像这样:

// ...previous function definitions from above
function addOneTimesTwo(x) {
  var holder = x;
  holder = addOne(holder);
  holder = timesTwo(holder);
  return holder;
}
console.log(addOneTimesTwo(3)); //8
console.log(addOneTimesTwo(4)); //10

在这种情况下,我们要做的是按照特定顺序将这两个函数手动组合在一起。 我们创建了一个新函数,该函数首先将要传递的值分配给一个holder变量,然后通过执行第一个函数,然后执行第二个函数来更新该变量的值,最后返回该holder的值。

(请注意,我们正在使用一个名为holder的变量来保存我们暂时传递的值。通过这样一个简单的函数,多余的局部变量可能看起来是多余的,但是即使在命令式JavaScript中,处理的值也是一个好习惯参数传递给函数,就好像它们是常量一样。可以在本地进行修改,但是这会导致在函数的不同阶段调用参数时值的困惑。

同样,如果我们想创建另一个新函数,以相反的顺序应用这两个较小的函数,则可以执行以下操作:

// ...previous function definitions from above
function timesTwoAddOne(x) {
  var holder = x;
  holder = timesTwo(holder);
  holder = addOne(holder);
  return holder;
}
console.log(timesTwoAddOne(3)); //7
console.log(timesTwoAddOne(4)); //9

当然,这段代码看起来很重复。 我们两个新组成的函数几乎完全相同,除了它们调用的两个较小函数的执行顺序不同。 我们需要将其干燥(例如不要重复自己)。 同样,使用临时变量来像这样改变它们的值不是很有功能,即使它被隐藏在我们正在创建的组合函数内部。

底线:我们可以做得更好。

创建功能撰写

让我们设计一个组合函数,它可以采用现有函数并将它们按我们想要的顺序组合在一起。 为了以一致的方式执行此操作而不必每次都使用内部函数,我们必须确定要作为参数传递函数的顺序。

我们有两个选择。 每个参数都是函数,它们可以从左到右或从右到左执行。 也就是说,使用我们建议的新函数compose(timesTwo, addOne)可能意味着timesTwo(addOne())从右向左读取参数,或者addOne(timesTwo())从左向右读取参数。

从左到右执行参数的好处是,它们将以与英语相同的方式读取,这与我们命名组合函数timesTwoAddOne()的方式非常timesTwoAddOne() ,以暗示乘法应该在加法之前进行。 我们都知道逻辑命名对于清除可读代码的重要性。

从左到右执行参数的缺点是要操作的值必须先到。 但是将值放在第一位使将来将结果函数与其他函数组合起来不太方便。 要很好地解释这种逻辑背后的思想,您无法击败Brian Lonsdorf的经典视频Hey Underscore,You're Doing It Wrong 。 (虽然应该指出的是,现在有用于下划线的Fp选项 ,有助于解决这一功能的编程问题,布莱恩与功能编程库,如演唱会使用下划线时讨论lodash-FPRamda 。)

无论如何,我们真正想要做的是先传递所有配置数据,最后传递要操作的值。 因此,定义我们的compose函数以读取其参数并从右到左应用它们是最有意义的。

因此,我们可以创建一个基本的compose函数,如下所示:

function compose(f1, f2) {
  return function(value) {
    return f1(f2(value));
  };
}

使用这个非常简单的compose函数,我们可以更简单地构造两个先前的复杂函数,并看到结果是相同的:

function addOne(x) {
  return x + 1;
}
function timesTwo(x) {
  return x * 2;
}
function compose(f1, f2) {
  return function(value) {
    return f1(f2(value));
  };
}
var addOneTimesTwo = compose(timesTwo, addOne);
console.log(addOneTimesTwo(3)); //8
console.log(addOneTimesTwo(4)); //10
var timesTwoAddOne = compose(addOne, timesTwo);
console.log(timesTwoAddOne(3)); //7
console.log(timesTwoAddOne(4)); //9

尽管此简单的compose函数有效,但并未考虑到许多限制其灵活性和适用性的问题。 例如,我们可能要组合两个以上的函数。 此外,我们一路走来也this一无所知。

我们可以解决这些问题,但是不必掌握合成的工作原理。 从自己的功能库(例如Ramda)继承更强大的compose可能比让我们自己动手更有效,默认情况下Ramda确实考虑了参数的从右到左排序。

类型是您的责任

重要的是要记住,程序员有责任了解所组成的每个函数返回的类型,以便下一个函数可以正确地处理它。 与执行严格类型检查的纯函数式编程语言不同,JavaScript不会阻止您尝试编写返回不正确类型值的函数。

您不仅限于传递数字,甚至不限于从一个函数到下一个函数维护相同类型的变量。 但是您有责任确保所编写的函数准备好处理上一个函数返回的任何值。

考虑观众

永远记住,将来可能会有其他人需要使用或修改您的代码。 对于不熟悉功能范例的程序员来说,在传统的JavaScript代码中使用组合可能显得很复杂。 目标是使代码更清晰,更易于阅读和维护。

但是随着ES2015语法的到来,甚至无需使用箭头函数的特殊compose方法,就可以将简单的组合函数创建为单行调用:

function addOne(x) {
  return x + 1;
}
function timesTwo(x) {
  return x * 2;
}
var addOneTimesTwo = x => timesTwo(addOne(x));
console.log(addOneTimesTwo(3)); //8
console.log(addOneTimesTwo(4)); //10

从今天开始作曲

与所有函数式编程技术一样,请务必记住所组成的函数应该是纯函数。 简而言之,这意味着每次将特定值传递给函数时,该函数应返回相同的结果,并且该函数不应产生会改变其自身外部值的副作用。

当您有一组要应用于数据的相关功能时,组合嵌套会非常方便,并且您可以将该功能的组件分解为可重用且易于组合的功能。

与所有函数式编程技术一样,我建议您明智地将组成部分撒入您现有的代码中以熟悉它。 如果操作正确,结果将更整洁,干燥,并使代码更具可读性。 那不是我们所有人想要的吗?

From: https://www.sitepoint.com/function-composition-building-blocks-for-maintainable-code/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值