对于 JavaScript 事件代码中的回调和参数,使用匿名函数而不是命名函数有什么好处?

IT技术 javascript coding-style callback anonymous-function code-readability
2021-03-09 12:44:05

我是 JavaScript 新手。我了解该语言的许多概念,我一直在阅读原型继承模型,并且我正在用越来越多的交互式前端内容来磨练我的口哨。这是一种有趣的语言,但我总是对许多非平凡交互模型的典型回调意大利面有点反感。

我一直觉得很奇怪的是,尽管 JavaScript 嵌套回调嵌套是可读性的噩梦,但我在许多示例和教程中很少看到的一件事是使用预定义的命名函数作为回调参数。白天,我是一名 Java 程序员,摒弃了关于代码单元的 Enterprise-y 名称的刻板印象,我开始喜欢使用具有大量功能性 IDE 的语言工作的一件事是使用有意义的,如果很长,名称可以使代码的意图和含义更加清晰,而不会增加实际工作效率。那么为什么在编写 JavaScript 代码时不使用相同的方法呢?

仔细想想,我可以提出支持和反对这个想法的论点,但是我对语言的天真和新奇使我无法得出任何关于为什么这在技术层面上会很好的结论。

优点:

  • 灵活性。具有回调参数的异步函数可以通过许多不同的代码路径之一访问,并且必须编写命名函数来解释每个可能的边缘情况可能会很麻烦。
  • 速度。它在很大程度上影响了黑客的心态。把东西固定在上面,直到它起作用为止。
  • 其他人都在做
  • 较小的文件大小,即使是微不足道的,但每一点都对网络很重要。
  • 更简单的 AST?我假设匿名函数是在运行时生成的,因此 JIT 不会将名称映射到指令,但我现在只是猜测。
  • 发货更快?也不确定这个。又猜了。

缺点:

  • 这是可怕的和不可读的
  • 当您在回调沼泽深处嵌套疯狂时,它会增加混乱(公平地说,这可能意味着您开始编写结构不佳的代码,但这很常见)。
  • 对于没有功能背景的人来说,理解它可能是一个奇怪的概念

有这么多现代浏览器显示出比以前更快地执行 JavaScript 代码的能力,我看不出使用匿名回调来获得任何微不足道的性能提升是多么必要。似乎,如果您处于使用命名函数可行的情况(可预测的行为和执行路径),那么就没有理由不这样做。

那么是否有任何我不知道的技术原因或陷阱使这种做法如此普遍是有原因的?

6个回答

我使用匿名函数有以下三个原因:

  1. 如果因为函数只在一个地方调用而不需要名称,那么为什么要向您所在的任何命名空间添加名称。
  2. 匿名函数被声明为内联,而内联函数的优势在于它们可以访问父作用域中的变量。是的,您可以为匿名函数命名,但如果它被声明为内联函数,则通常毫无意义。所以内联有一个显着的优势,如果你在做内联,就没有理由在上面命名。
  3. 当在调用它们的代码中定义处理程序时,代码似乎更加独立和可读。您可以几乎按顺序阅读代码,而不必去查找具有该名称的函数。

我确实尽量避免匿名函数的深层嵌套,因为这可能很难理解和阅读。通常当这种情况发生时,有一种更好的方法来构建代码(有时使用循环,有时使用数据表等),命名函数通常也不是解决方案。

我想我会补充一点,如果回调开始超过大约 15-20 行并且不需要直接访问父作用域中的变量,我会很想给它一个名字并将其分解为它是在别处声明的自己命名的函数。这里肯定有一个可读性点,如果将一个很长的非平凡函数放在自己的命名单元中,它会更易于维护。但是,我最终得到的大多数回调都没有那么长,而且我发现将它们保持内联更具可读性。

1. 这个论点似乎可以扩展到涵盖我们命名的所有事物。2. 这个论点似乎有点循环,但我想我明白你在说什么。3. 目标是为函数提供非常清晰、有意义的名称,这样您就不必去找它们了。
2021-04-21 12:44:05
@DougStephen - 在第 1 点,我只是说当回调被声明为内联时,给它一个名字并不能真正为您提供任何额外的信息。您可以从看到和声明它的上下文中确切地知道它的用途。也许这是一种学习的解释,你在看到很多构造之后已经习惯了,但是现在当我看到它时,它在我看来与for循环没有什么不同,我认为for不需要打破循环并给出一个名称可读。
2021-05-07 12:44:05
@DougStephen - 另外,不要低估第 2 点的优势。通过访问父作用域变量,而不是必须将所有内容放入对象并将它们作为参数传递,可以更简单地编写某些类型的代码。这很方便的最常见的地方是当回调想要this从原始函数访问指针时。您可以将其存储在父函数的局部变量中(通常这样做var self = this;,然后您可以self在匿名函数中访问。非常非常方便。
2021-05-09 12:44:05
我明白你在说什么。这可能符合命名函数仅适用于某些上下文(可预测行为)的想法。在这种情况下,您将不会创建内联函数,因此您可以命名它。显然,将函数命名为内联是愚蠢的。
2021-05-12 12:44:05
我看它用在哪里,我不会不同意你的。但它似乎也有点副作用。将这样的信息作为参数发送下来可能会很“麻烦”,但对我来说似乎也更有控制力。即使在使用像 C 这样的语言工作时,我也不喜欢访问外部作用域变量。
2021-05-13 12:44:05

我自己更喜欢命名函数,但对我来说,这归结为一个问题:

我会在其他任何地方使用此功能吗?

如果答案是肯定的,我会命名/定义它。如果没有,请将其作为匿名函数传递。

如果你只使用它一次,用它挤满全局命名空间是没有意义的。在当今复杂的前端中,本来可以匿名的命名函数的数量迅速增长(在真正复杂的设计中很容易超过 1000),通过首选匿名函数导致(相对)较大的性能提升。

但是,代码的可维护性也极其重要。每种情况都不同。如果您一开始没有编写很多这些函数,那么无论哪种方式都没有坏处。这真的取决于你的喜好。

关于名字的另一个注意事项。养成定义长名称的习惯真的会损害您的文件大小。以下面的例子为例。

假设这两个函数都做同样的事情:

function addTimes(time1, time2)
{
    // return time1 + time2;
}

function addTwoTimesIn24HourFormat(time1, time2)
{
    // return time1 + time2;
}

第二个确切地告诉您它在名称中的作用。第一个比较模糊。但是,名称有 17 个字符的不同。假设该函数在整个代码中被调用了 8 次,这就是您的代码不需要153 个额外字节不是很大,但如果这是一种习惯,将其推断为 10 或什至 100 的功能将很容易意味着下载量有几 KB 的差异。

然而,同样需要在可维护性与性能优势之间进行权衡。这是处理脚本语言的痛苦。

我要补充一下.. 使用匿名功能的人不会考虑产品的未来开发或支持。一个函数是用来重用的,你今天写的东西,仅仅因为你只使用一次,并不意味着其他人以后不需要它。我发现匿名函数适合懒惰的开发人员,我拒绝使用它们。
2021-04-24 12:44:05
如果您要最小化 javascript 以进行部署,则有关名称大小的点不再相关。在这种情况下选择可读性。
2021-05-20 12:44:05
仅当您的函数/变量在闭包内定义时(它们应该是)。任何看起来对 minifyer 可能是全局的都将被保留,名称的长度仍然很重要。
2021-05-21 12:44:05

聚会有点晚了,但一些尚未提到的功能方面,匿名或其他......

匿名函数在团队中关于代码的类人对话中不容易被提及。例如,“乔,你能解释一下算法在那个函数中的作用吗。......哪个?fooApp 函数中的第 17 个匿名函数......不,不是那个!第 17 个!”

Anon funcs 对于调试器也是匿名的。(废话!)因此,调试器堆栈跟踪通常只会显示一个问号或类似的东西,当您设置多个断点时,它会变得不那么有用。您遇到了断点,但发现自己向上/向下滚动调试窗口以找出您在程序中的位置,因为嘿,问号功能就是不这样做!

关于污染全局命名空间的担忧是有效的,但可以通过将函数命名为您自己的根对象中的节点来轻松解决,例如“myFooApp.happyFunc = function ( ... ) { ... }; ”。

在全局命名空间中可用的函数,或者像上面一样作为根对象中的节点,可以在开发和调试期间直接从调试器调用。例如,在控制台命令行中,执行“myFooApp.happyFunc(42)”。这是一种极其强大的能力,在编译型编程语言中(本机)不存在。用 anon func 试试。

通过将匿名函数分配给 var,然后将 var 作为回调(而不是内联)传递,可以使它们更具可读性。例如: var funky = function ( ... ) { ... }; jQuery('#otis').click(funky);

使用上述方法,您可以潜在地将几个匿名函数分组在父函数的顶部,然后在其下方,顺序语句的内容变得更加紧密,并且更易于阅读。

使用命名函数更具可读性,并且它们还能够自我引用,如下例所示。

(function recursion(iteration){
    if (iteration > 0) {
      console.log(iteration);
      recursion(--iteration);
    } else {
      console.log('done');
    }
})(20);

console.log('recursion defined? ' + (typeof recursion === 'function'));

http://jsfiddle.net/Yq2WD/

当您想要一个立即调用的函数引用自身但不添加到全局命名空间时,这很好。它仍然可读但没有污染。有你的蛋糕,吃它。

你好,我的名字是 Jason 或者你好,我的名字是 ???? 你选。

匿名函数很有用,因为它们可以帮助您控制公开哪些函数。

更多细节:如果没有名称,除了创建它的确切位置之外,您不能在任何地方重新分配或篡改它。一个好的经验法则是,如果您不需要在任何地方重用这个函数,最好考虑匿名函数是否能更好地防止在任何地方被篡改。

示例:如果您正在处理一个有很多人的大型项目,如果您在一个更大的函数中包含一个函数并且您给它命名会怎样?这意味着与您一起工作并在较大函数中编辑代码的任何人都可以随时对较小的函数进行操作。例如,如果您将其命名为“add”,而有人在同一范围内将“add”重新分配给一个数字,该怎么办?然后整个事情就崩溃了!

PS - 我知道这是一个很老的帖子,但是这个问题有一个更简单的答案,我希望有人在我作为初学者寻找答案时这样说 - 我希望你能恢复一个旧线程!

嘿克里斯!也感谢您的反馈。匿名函数与 IIFE 不同,但这篇文章很值得分享。尽管如此,匿名函数仍然很适合了解它们的工作原理以及它们对初学者学习基础知识的作用。特别是如果他们还没有学习模块。
2021-04-22 12:44:05
感谢林赛指出这一点——你当然是对的。同意这些都是值得学习的重要概念。为了完整起见,这里有另一篇文章讨论了这两个概念之间的异同:medium.com/@DaphneWatson/...
2021-05-09 12:44:05
您建议的大部分内容都可以通过letconst范围来完成在 2021 年使用 IIFE 的理由并不多。请参阅:levelup.gitconnected.com/...
2021-05-14 12:44:05