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

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

本文关键字:函数 高阶 有用 存储 为什么 变量      更新时间:2023-09-26

我正在学习函数式编程,有一个问题。

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

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只是一个变量,而不是一个函数。

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

所以我要问的是为什么要这样做?没有括号的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");

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

将函数赋值给变量的原因与将值赋值给变量的原因相同。该变量是一个带有标签的意图描述符

如果你读了animals.filter(isDog),那么应该是非常明显的从函数中期望得到什么。

也就是说,代码中的期望是,您正在获取动物的集合,并且进行过滤,使其只包括是狗的动物。

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

以以下行为例:

var fiveMinutesAsMilliseconds = 5 * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;

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

如果你的函数代码是模糊的,那么很可能是因为你的变量命名不好。命名是很困难的,所以如果变量命名得不好,就把它当作一个bug来处理,并修复这些名字,这样代码就可以理解了。

将一个匿名函数赋值给一个变量只有在你打算多次使用该函数时才有意义。否则,您可以将其作为参数直接传递给filter()调用。此外,当使用filter()这样的方法时,最好使用箭头函数。作为旁注,如果您不打算稍后重新分配变量,最好使用const而不是var –这将防止您错误地重新分配该变量。在ES6中,我应该使用多少let和const ?

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

你的代码可以是这样的:

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

但是现在匿名函数是一个变量,并且我们不使用括号(如isDog())来编写它,直观地看,这似乎使代码的可读性降低了。

这实际上取决于函数。你需要关心的是filter要求的是什么。filter需要一个函数,如果应用isDog()(除了将抛出的错误),将返回一个函数而不是

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

let greaterThan = x => y => y > x
[1,2,3,4,5,6].filter(greaterThan(3))
// => [ 4, 5, 6 ]
因为函数不仅可以赋值给变量,还可以是函数应用程序的返回值。

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

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'检查


乍一看,我认为isDog只是一个变量,而不是一个函数。

即使我们可以推断它是一个函数,因为它附加在过滤器上,它仍然令人困惑,我想还有其他不明显的情况。

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


那么现在我们需要查找isDog是/做什么的

嗯,不一定。惯例可以大有作为。

  • 用动词来命名你的函数:encode, concat (enate), write, reduce, parse,等等

  • 返回布尔值的函数通常以is - Array.isArray, Number.isNaN, Number.isFinite等为前缀

  • onhandle前缀命名事件处理程序(函数)- onClick, handleClick

  • class名称应该是单数名词

  • 还有很多…

有很多这样的小事。你不必遵循别人的惯例;虽然从长远来看,这可能会对你有帮助。最重要的是,采用某种惯例并坚持下去。