为什么将匿名函数存储为变量并将其传递给高阶函数是有用的?

Why is it useful to have an anonymous function stored as a variable, and pass it to higher order functions?

我正在研究我对函数式编程的理解并有一个问题。

在这个例子中*一个匿名函数被分配给变量isDog,然后isDog被传递给过滤器。然后我们可以将动物数组中的所有狗过滤到一个新数组中。 (我知道我可以缩短这段代码,但这不是 post 的重点:))

var animals = [{name: "Spot", species: "dog"}, 
{name: "Rex", species: "dog"}, 
{name: "Whiskers", species: "cat"},
{name: "Floppy", species: "Rabbit"}]

var isDog = function(animal) {return animal.species === 'dog' } 

var dogs = animals.filter(isDog)

我知道函数可以作为参数传递,也许这可以通过将函数分配给变量来明确表达。

但是现在匿名函数是一个变量,我们没有用括号写它,即 isDog(),直觉上这似乎降低了代码的可读性。乍一看,我会假设 isDog 只是一个变量,而不是一个函数。

即使我们可以推断它是一个函数,因为它附加到过滤器,它仍然令人困惑,我假设还有其他情况不明显。所以现在我们必须查找什么是 Dog is/does。

所以我想问的是为什么要这样做?没有括号的 isDog 看起来确实更优雅,但是以这种方式使用它有任何技术原因吗?

请注意,我了解函数作为参数的强大功能,我的问题更多是关于为什么将它们分配给一个变量,如果它会产生模棱两可的代码。

谢谢。

*改编自这个有用的视频。大约 8 分钟标记,https://www.youtube.com/watch?v=BMUiFMZr7vk

括号仅在您调用函数时出现。如果将带括号的调用传递给 .filter() 方法,则实际上传递的是调用的结果,而不是指向调用本身的指针。另一种方法是将整个函数传递给过滤器函数,但可读性往往会受到影响。它还限制了您修改要放入的函数中的内容的能力。

想象一下这样一种情况,您可能想要进行排序而不是筛选,并且您想要根据传入的布尔值更改排序。

以这个基本排序为例:

var numbers = [4, 2, 5, 1, 3];

numbers.sort(function(a, b) {
  return a - b;
});

如果您希望能够根据另一个变量选择排序方向怎么办。您可以定义两个排序函数,然后为您想要的排序传递正确的函数。

var numbers = [4, 2, 5, 1, 3];
var sortAscending = function(a, b) {
  return a - b;
}

var sortDescending = function(a, b) {
  return b - a;
}

function doSort(myArray, dir) {
    var sortMethod = (dir == "asc") ? sortAscending : sortDescending;
    myArray.sort(sortMethod );
}

doSort(numbers, "asc");

上面说明了以这种方式传递方法调用如何在需要时提供更大的灵活性,并确保仅在排序内部执行时才进行调用。

仅当您打算多次使用该函数时,将匿名函数分配给变量才有意义。否则,您可以将其作为参数直接传递给 filter() 调用。此外,当使用 filter() 之类的方法时,最好使用 arrow functions. And as a side note, if you don't intend to reassign a variable later, it's better to use const instead of var – this will prevent you from reassigning that variable by mistake. See How much should I be using 'let' vs 'const' in ES6?.

为了解决您对为什么 isDog 没有括号的困惑:括号意味着您调用函数,而在这里您只是将此函数作为参数传递。 filter(isDog()) 意味着您正在调用 isDog 函数,然后将返回值作为参数传递给 filter() 方法。这只有在 isDog 返回另一个函数时才有意义。

您的代码可能如下所示:

const dogs = animals.filter(animal => animal.species === 'dog')

我们将函数分配给变量的原因与我们将值分配给变量的原因相同。该变量是一个 标记的意图描述符

如果您阅读 animals.filter(isDog),它应该 清楚地表明函数的作用。

也就是说,代码中的期望是你正在收集 animals 并且 filtering 它只包含是狗的动物

这与使用任何其他值的变量没有什么不同。

以下面一行为例:

var fiveMinutesAsMilliseconds = 5 * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;

您应该能够阅读那行代码并理解 fiveMinutesAsMilliseconds 将包含什么样的值 而无需 查找 SECONDS_PER_MINUTEMILLISECONDS_PER_SECOND.

如果您的函数代码含糊不清,那么很可能是因为您的变量命名不当。 Naming things is hard,因此如果变量命名错误,请将其视为错误并修复名称以使代码易于理解。

But now that the anonymous function is a variable and we don't write it with brackets i.e isDog(), intuitively it seems like this has made the code less readable.

嗯,这实际上取决于功能。您需要关心的是 filter 所要求的。 filter 需要一个函数,如果你应用 isDog()(除了将抛出的错误),函数将 不会 被 returned .

有时您会在 filter 的应用程序中看到括号 ()

let greaterThan = x => y => y > x

[1,2,3,4,5,6].filter(greaterThan(3))
// => [ 4, 5, 6 ]

...因为不仅可以将函数赋值给变量,它们还可以是来自函数应用程序的return值。


So what I am asking is why do this? It does look more elegant to have isDog without brackets, but is there any technical reason to use it this way?

好吧,让 isDog 成为一个单独的函数并没有使代码“看起来不错”的主要目标——有些人甚至可能会争辩说它更冗长,他们可能不喜欢它。

使 isDog 成为其自身功能的主要原因是因为它本身就很有用。也许你有一只 x 并且想知道它是不是一只狗?

isDog(x); // => true
isDog(y); // => false

如果 isDog 不是它自己的函数,您的程序将充满重复的 is-dog 检查代码

// ex1
animals.filter(animal => animal.species === 'dog')

// ex2
if (x.species === 'dog') {
  throw Error('Dogs are not allowed !')
}

// ex3
function canCooperate (a,b) {
  switch (true) {
    case a.species === 'cat' && b.species === 'dog':
    case a.species === 'dog' && b.species === 'cat':
      return false
    default:
      return true
  }
}

将定义动物 种类 的复杂性包装在它自己的可重用函数中要好得多。

const isDog = ({species}) => species === 'dog'
const isCat = ({species}) => species === 'cat'

// ex1
let allDogs = animals.filter(isDog)

// ex2
if (isDog(x)) {
  throw Error('Dogs are not allowed !')
}

// ex3
function canCooperate (a,b) {
  switch (true) {
    case isCat(a) && isDog(b):
    case isDog(a) && isCat(b):
      return false
    default:
      return true
  }
}

isDogx.species === 'dog' 更具描述性,更清楚地传达了代码的目的。事实上,最好在任何地方都使用 isDog,因为可能存在其他标准会改变狗的含义。

const isDog = x => x.species === 'dog' || x.species === 'wolf'

也许不是最好的例子,但认为可能存在一些你想对狼一视同仁的极端情况并不完全牵强附会。如果您没有在自己的函数 isDog 中编写此代码,则必须更新应用程序中的 段代码以包含额外的 ... || x.species === 'wolf' 检查


At first glance I would assume isDog is just a variable, and not a function.

Even if we can infer it is a function because it is attached to filter, it is still confusing and I assume there are other cases where it is not obvious.

通常函数都有一个动词名称,但情况并非总是如此。 filter 既是名词又是动词,这使得这个论点很难成立,但它主要是规则的例外。我知道你有点承认这一点,但简短的回答是:你不知道。有人可能是个真正的混蛋,并命名一个函数 animals,它仍然有效 JavaScript.


So now we have to look up what isDog is/does.

嗯,不一定。公约可以走很长的路。

  • 使用动词命名函数:encodeconcat(enate)、writereduceparse、等等

  • return 布尔值通常以 is 为前缀的函数 – Array.isArrayNumber.isNaNNumber.isFinite

  • 名称事件处理程序(函数)带有 onhandle 前缀 – onClickhandleClick

  • class名字应该是单数名词

  • 列表还在继续......

像这样的小事数不胜数。而且您不必遵循其他人的惯例;它可能会在长期 运行 中对您有所帮助。最重要的是,采用某种惯例并坚持下去。