简体   繁体   English

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

[英]What are the benefits to using anonymous functions instead of named functions for callbacks and parameters in JavaScript event code?

I'm new-ish to JavaScript.我是 JavaScript 新手。 I understand many of the concepts of the language, I've been reading up on the prototype inheritance model, and I'm whetting my whistle with more and more interactive front-end stuff.我了解该语言的许多概念,我一直在阅读原型继承模型,并且我正在用越来越多的交互式前端内容来吹口哨。 It's an interesting language, but I'm always a bit turned off by the callback spaghetti that is typical of many non-trivial interaction models.这是一种有趣的语言,但我总是对许多非平凡交互模型中典型的回调意大利面有点反感。

Something that has always seemed strange to me is that in spite of the readability nightmare that is a nest of JavaScript nested callbacks, the one thing that I very rarely see in many examples and tutorials is the use of predefined named functions as callback arguments.我一直觉得很奇怪的是,尽管 JavaScript 嵌套回调嵌套的可读性噩梦,但我在许多示例和教程中很少看到的一件事是使用预定义的命名函数作为回调参数。 I'm a Java programmer by day, and discarding the stereotypical jabs about Enterprise-y names for units of code one of the things I've come to enjoy about working in a language with a strong selection of featureful IDE's is that using meaningful, if long, names can make the intent and meaning of code much clearer without making it more difficult to actually be productive.白天,我是一名 Java 程序员,摒弃了关于代码单元的 Enterprise-y 名称的刻板印象,我开始享受使用具有大量功能 IDE 选择的语言工作的一件事是使用有意义的,如果很长,名称可以使代码的意图和含义更加清晰,而不会增加实际工作效率。 So why not use the same approach when writing JavaScript code?那么为什么在编写 JavaScript 代码时不使用相同的方法呢?

Giving it thought, I can come up with arguments that are both for and against this idea, but my naivety and newness to the language impairs me from reaching any conclusions as to why this would be good at a technical level.仔细想想,我可以提出支持和反对这个想法的论点,但我对语言的天真和新奇使我无法得出任何关于为什么这在技术层面上会很好的结论。

Pros:优点:

  • Flexibility.灵活性。 An asynchronous function with a callback parameter could be reached by one of many different code paths and it could be harried to have to write a named function to account for every single possible edge case.具有回调参数的异步函数可以通过许多不同的代码路径之一访问,并且必须编写命名函数来解释每个可能的边缘情况可能会很麻烦。
  • Speed.速度。 It plays heavily in to the hacker mentality.它在很大程度上影响了黑客的心态。 Bolt things on to it until it works.把东西固定在上面,直到它起作用为止。
  • Everyone else is doing it其他人都在做
  • Smaller file sizes, even if trivially so, but every bit counts on the web.较小的文件大小,即使是微不足道的,但每一点都对网络很重要。
  • Simpler AST?更简单的 AST? I would assume that anonymous functions are generated at runtime and so the JIT won't muck about with mapping the name to instructions, but I'm just guessing at this point.我假设匿名函数是在运行时生成的,因此 JIT 不会将名称映射到指令,但我现在只是猜测。
  • Quicker dispatching?发货更快? Not sure about this one either.也不确定这个。 Guessing again.又猜了。

Cons:缺点:

  • It's hideous and unreadable这是可怕的和不可读的
  • It adds to the confusion when you're nested nuts deep in a swamp of callbacks (which, to be fair, probably means you're writing poorly constructed code to begin with, but it's quite common).当您在回调沼泽深处嵌套疯狂时,它会增加混乱(公平地说,这可能意味着您开始编写结构不佳的代码,但这很常见)。
  • For someone without a functional background it can be a bizarre concept to grok对于没有功能背景的人来说,理解它可能是一个奇怪的概念

With so many modern browsers showing the ability to execute JavaScript code much faster than before, I'm failing to see how any trivial sort of performance gain one might get out using anonymous callbacks would be a necessity.有这么多现代浏览器显示出比以前更快地执行 JavaScript 代码的能力,我看不出使用匿名回调来获得任何微不足道的性能提升是多么必要。 It seems that, if you are in a situation where using a named function is feasible (predictable behavior and path of execution) then there would be no reason not to.似乎,如果您处于使用命名函数可行的情况(可预测的行为和执行路径),那么没有理由不这样做。

So are there any technical reasons or gotchas that I'm not aware of that makes this practice so commonplace for a reason?那么是否有任何我不知道的技术原因或陷阱使这种做法如此普遍是有原因的?

I use anonymous functions for three reasons:我使用匿名函数有以下三个原因:

  1. If no name is needed because the function is only ever called in one place, then why add a name to whatever namespace you're in.如果因为函数只在一个地方被调用而不需要名称,那么为什么要向您所在的任何命名空间添加一个名称。
  2. Anonymous functions are declared inline and inline functions have advantages in that they can access variables in the parent scopes.匿名函数被声明为内联,而内联函数的优势在于它们可以访问父作用域中的变量。 Yes, you can put a name on an anonymous function, but that's usually pointless if it's declared inline.是的,您可以为匿名函数命名,但如果它被声明为内联函数,则通常毫无意义。 So inline has a significant advantage and if you're doing inline, there's little reason to put a name on it.所以内联有一个显着的优势,如果你正在做内联,就没有理由在上面放一个名字。
  3. The code seems more self-contained and readable when handlers are defined right inside the code that's calling them.当在调用它们的代码中定义处理程序时,代码似乎更加独立和可读。 You can read the code in almost sequential fashion rather than having to go find the function with that name.您可以几乎按顺序阅读代码,而不必去查找具有该名称的函数。

I do try to avoid deep nesting of anonymous functions because that can be hairy to understand and read.我确实尽量避免匿名函数的深层嵌套,因为这可能很难理解和阅读。 Usually when that happens, there's a better way to structure the code (sometimes with a loop, sometimes with a data table, etc...) and named functions isn't usually the solution there either.通常,当这种情况发生时,有更好的方法来构建代码(有时使用循环,有时使用数据表等),命名函数通常也不是解决方案。

I guess I'd add that if a callback starts to get more than about 15-20 lines long and it doesn't need direct access to variables in the parent scope, I would be tempted to give it a name and break it out into it's own named function declared elsewhere.我想我会补充一点,如果回调开始超过大约 15-20 行并且不需要直接访问父作用域中的变量,我会很想给它一个名字并将其分解为它是在别处声明的自己命名的函数。 There is definitely a readability point here where a non-trivial function that gets long is just more maintainable if it's put in its own named unit.这里肯定有一个可读性点,如果将一个很长的非平凡函数放在自己的命名单元中,它会更易于维护。 But, most callbacks I end up with are not that long and I find it more readable to keep them inline.但是,我最终得到的大多数回调都没有那么长,我发现将它们保持内联更具可读性。

I prefer named functions myself, but for me it comes down to one question:我自己更喜欢命名函数,但对我来说,这归结为一个问题:

Will I use this function anywhere else?我会在其他任何地方使用此功能吗?

If the answer is yes, I name/define it.如果答案是肯定的,我会命名/定义它。 If not, pass it as an anonymous function.如果没有,请将其作为匿名函数传递。

If you only use it once, it doesn't make sense to crowd the global namespace with it.如果你只使用它一次,用它挤满全局命名空间是没有意义的。 In today's complex front-ends, the number of named functions that could have been anonymous grows quickly (easily over 1000 on really intricate designs), resulting in (relatively) large performance gains by preferring anonymous functions.在当今复杂的前端中,本来可以匿名的命名函数的数量迅速增长(在非常复杂的设计中很容易超过 1000),通过首选匿名函数导致(相对)较大的性能提升。

However, code maintainability is also extremely important.但是,代码的可维护性也极其重要。 Each situation is different.每种情况都不同。 If you're not writing a lot of these functions to begin with, there's no harm in doing it either way.如果您一开始没有编写很多这些函数,那么无论哪种方式都没有坏处。 It's really up to your preference.这真的取决于你的喜好。

Another note about names.关于名字的另一个注意事项。 Getting in the habit of defining long names will really hurt your file size.养成定义长名称的习惯真的会损害您的文件大小。 Take the following example.以下面的例子为例。

Assume both of these functions do the same thing:假设这两个函数做同样的事情:

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

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

The second tells you exactly what it does in the name.第二个确切地告诉您它在名称中的作用。 The first is more ambiguous.第一个比较模糊。 However, there are 17 characters of difference in the name.但是,名称有 17 个字符的差异。 Say the function is called 8 times throughout the code, that's 153 extra bytes your code didn't need to have.假设该函数在整个代码中被调用了 8 次,这就是您的代码不需要的153 个额外字节 Not colossal, but if it's a habit, extrapolating that to 10s or even 100s of functions will easily mean a few KB of difference in the download.不是很大,但如果这是一种习惯,将其推断为 10 或什至 100 的功能将很容易意味着下载量有几 KB 的差异。

Again however, maintainability needs to be weighed against the benefits of performance.然而,同样需要在可维护性与性能优势之间进行权衡。 This is the pain of dealing with a scripted language.这是处理脚本语言的痛苦。

A bit late to the party, but some not yet mentioned aspects to functions, anonymous or otherwise...聚会有点晚了,但一些尚未提到的功能方面,匿名或其他方式......

Anon funcs are not easily referred to in humanoid conversations about code, amongst a team.匿名函数在团队中关于代码的类人对话中不容易被提及。 Eg, "Joe, could you explain what the algorithm does, within that function. ... Which one? The 17th anonymous function within the fooApp function. ... No, not that one! The 17th one!"例如,“Joe,你能解释一下算法在那个函数中做了什么。......哪个?fooApp 函数中的第 17 个匿名函数......不,不是那个!第 17 个!”

Anon funcs are anonymous to the debugger as well. Anon funcs 对于调试器也是匿名的。 (duh!) Therefore, the debugger stack trace will generally just show a question mark or similar, making it less useful when you have set multiple breakpoints. (废话!)因此,调试器堆栈跟踪通常只会显示一个问号或类似的东西,当您设置多个断点时,它会变得不那么有用。 You hit the breakpoint, but find yourself scrolling the debug window up/down to figure out where the hell you are in your program, because hey, question mark function just doesn't do it!您遇到了断点,但发现自己向上/向下滚动调试窗口以找出您在程序中的位置,因为嘿,问号功能就是不这样做!

Concerns about polluting the global namespace are valid, but easily remedied by naming your functions as nodes within your own root object, like "myFooApp.happyFunc = function ( ... ) { ... }; ".关于污染全局命名空间的担忧是有效的,但可以通过将函数命名为您自己的根对象中的节点来轻松解决,例如“myFooApp.happyFunc = function ( ... ) { ... }; ”。

Functions that are available in the global namespace, or as nodes in your root object like above, can be invoked from the debugger directly, during development and debug.在全局命名空间中可用的函数,或者像上面一样作为根对象中的节点,可以在开发和调试期间直接从调试器调用。 Eg, at the console command line, do "myFooApp.happyFunc(42)".例如,在控制台命令行,执行“myFooApp.happyFunc(42)”。 This is an extremely powerful ability that does not exist (natively) in compiled programming languages.这是一种极其强大的能力,在编译的编程语言中(原生)是不存在的。 Try that with an anon func.用 anon func 试试。

Anon funcs can be made more readable by assigning them to a var, and then passing the var as the callback (instead of inlining).通过将匿名函数分配给 var,然后将 var 作为回调传递(而不是内联),可以使匿名函数更具可读性。 Eg: var funky = function ( ... ) { ... };例如: var funky = function ( ... ) { ... }; jQuery('#otis').click(funky); jQuery('#otis').click(funky);

Using the above approach, you could potentially group several anon funcs at the top of the parental func, then below that, the meat of sequential statements becomes much tighter grouped, and easier to read.使用上述方法,您可以潜在地将几个匿名函数分组在父函数的顶部,然后在其下方,顺序语句的内容变得更加紧密,并且更易于阅读。

Its more readable using named functions and they are also capable of self-referencing as in the example below.使用命名函数更具可读性,并且它们还能够自我引用,如下例所示。

(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/ http://jsfiddle.net/Yq2WD/

This is nice when you want to have an immediately invoked function that references itself but does not add to the global namespace.当您想要一个立即调用的函数引用自身但不添加到全局命名空间时,这很好。 It's still readable but not polluting.它仍然可读但没有污染。 Have your cake and eat it to.有你的蛋糕,吃它。

Hi, my name is Jason OR hi, my name is ????你好,我的名字是 Jason 或者你好,我的名字是 ???? you pick.你选。

Anonymous functions are useful because they help you control which functions are exposed.匿名函数很有用,因为它们可以帮助您控制公开哪些函数。

More Detail: If there is no name, you can't reassign it or tamper with it anywhere but the exact place it was created.更多细节:如果没有名称,除了创建它的确切位置之外,您不能在任何地方重新分配或篡改它。 A good rule of thumb is, if you don't need to re-use this function anywhere, it's a good idea to consider if an anonymous function would be better to prevent getting tampered with anywhere.一个好的经验法则是,如果您不需要在任何地方重用这个函数,最好考虑匿名函数是否能更好地防止在任何地方被篡改。

Example: If you're working on a big project with a lot of people, what if you have a function inside of a bigger function and you name it something?示例:如果您正在处理一个有很多人的大型项目,如果您在一个更大的函数中包含一个函数并且您给它命名会怎样? That means anyone working with you and also editing code in the bigger function can do stuff to that smaller function at any time.这意味着与您一起工作并在较大函数中编辑代码的任何人都可以随时对较小的函数进行操作。 What if you named it "add" for instance, and someone reassigned "add" to a number instead inside the same scope?例如,如果您将其命名为“add”,而有人在同一范围内将“add”重新分配给一个数字,该怎么办? Then the whole thing breaks!然后整个事情就崩溃了!

PS -I know this is a very old post, but there is a much simpler answer to this question and I wish someone had put it this way when I was looking for the answer myself as a beginner- I hope you're ok with reviving an old thread! PS - 我知道这是一个很老的帖子,但是这个问题有一个更简单的答案,我希望有人在我作为初学者寻找答案时这样说 - 我希望你能恢复一个旧线程!

Well, just to be clear for the sake of my arguments, the following are all anonymous functions/function expressions in my book:好吧,为了我的论点清楚起见,以下都是我书中的匿名函数/函数表达式:

var x = function(){ alert('hi'); },

indexOfHandyMethods = {
   hi: function(){ alert('hi'); },
   high: function(){
       buyPotatoChips();
       playBobMarley();
   }
};

someObject.someEventListenerHandlerAssigner( function(e){
    if(e.doIt === true){ doStuff(e.someId); }
} );

(function namedButAnon(){ alert('name visible internally only'); })()

Pros:优点:

  • It can reduce a bit of cruft, particularly in recursive functions (where you could (should actually since arguments.callee is deprecated) still use a named reference per the last example internally), and makes it clear the function only ever fires in this one place.它可以减少一些麻烦,特别是在递归函数中(你可以(实际上应该因为 arguments.callee 已被弃用)仍然在内部使用最后一个示例的命名引用),并明确该函数只在这个函数中触发地方。

  • Code legibility win: in the example of the object literal with anon funcs assigned as methods, it would be silly to add more places to hunt and peck for logic in your code when the whole point of that object literal is to plop some related functionality in the same conveniently referenced spot.代码易读性获胜:在将匿名函数分配为方法的对象字面量的示例中,当该对象字面量的全部目的是在其中添加一些相关功能时,在代码中添加更多位置来寻找和啄食逻辑是愚蠢的同一个方便引用的地方。 When declaring public methods in a constructor, however, I do tend to define labeled functions inline and then assign as references of this.sameFuncName.然而,在构造函数中声明公共方法时,我确实倾向于定义内联标记函数,然后将其分配为 this.sameFuncName 的引用。 It lets me use the same methods internally without the 'this.'它让我在内部使用相同的方法而无需“this”。 cruft and makes order of definition a non-concern when they call each other. cruft 并在他们互相调用时使定义的顺序变得无关紧要。

  • Useful for avoiding needless global namespace pollution - internal namespaces, however, shouldn't ever be that broadly filled or handled by multiple teams simultaneously so that argument seems a bit silly to me.对于避免不必要的全局命名空间污染很有用 - 但是,内部命名空间不应该被多个团队同时广泛填充或处理,因此这个论点对我来说似乎有点愚蠢。

  • I agree with the inline callbacks when setting short event handlers.在设置短事件处理程序时,我同意内联回调。 It's silly to have to hunt for a 1-5 line function, especially since with JS and function hoisting, the definitions could end up anywhere, not even within the same file.必须寻找 1-5 行函数是愚蠢的,尤其是因为使用 JS 和函数提升,定义可能会在任何地方结束,甚至不在同一个文件中。 This could happen by accident without breaking anything and no, you don't always have control of that stuff.这可能是偶然发生的,不会破坏任何东西,不,你并不总是可以控制那些东西。 Events always result in a callback function being fired.事件总是导致回调函数被触发。 There's no reason to add more links to the chain of names you need to scan through just to reverse engineer simple event-handlers in a large codebase and the stack trace concern can be addressed by abstracting event triggers themselves into methods that log useful info when debug mode is on and fire the triggers.没有理由添加更多链接到您需要扫描的名称链,只是为了在大型代码库中对简单的事件处理程序进行逆向工程,并且可以通过将事件触发器自身抽象为在调试时记录有用信息的方法来解决堆栈跟踪问题模式打开并触发触发器。 I'm actually starting to build entire interfaces this way.我实际上开始以这种方式构建整个界面。

  • Useful when you WANT the order of function definition to matter.当您希望函数定义的顺序很重要时很有用。 Sometimes you want to be certain a default function is what you think it is until a certain point in the code where it's okay to redefine it.有时您想确定一个默认函数是您认为的那样,直到代码中的某个点可以重新定义它。 Or you want breakage to be more obvious when dependencies get shuffled.或者您希望在依赖项被改组时破坏更加明显。

Cons:缺点:

  • Anon functions can't take advantage of function hoisting.匿名函数不能利用函数提升。 This is a major difference.这是一个主要区别。 I tend to take heavy advantage of hoisting to define my own explicitly named funcs and object constructors towards the bottom and get to the object definition and main-loop type stuff right up at the top.我倾向于充分利用提升来在底部定义我自己的显式命名的函数和对象构造函数,并在顶部获得对象定义和主循环类型的东西。 I find it makes the code easier to read when you name your vars well and get a broad view of what's going on before ctrl-Fing for details only when they matter to you.我发现当你为你的变量命名时,它会让代码更容易阅读,并且只有当它们对你很重要时才能在 ctrl-Fing 之前对发生的事情有一个广泛的了解。 Hoisting can also be a huge benefit in heavily event-driven interfaces where imposing a strict order of what's available when can bite you in the butt.在高度事件驱动的接口中,提升也可以是一个巨大的好处,在这种接口中,对可用的东西施加严格的顺序可能会让你大吃一惊。 Hoisting has its own caveats (like circular reference potential) but it is a very useful tool for organizing and making code legible when used right.提升有它自己的警告(如循环引用潜力),但它是一个非常有用的工具,用于组织和使代码在正确使用时清晰易读。

  • Legibility/Debug.易读性/调试。 Absolutely they get used way too heavily at times and it can make debug and code legibility a hassle.当然,它们有时会被过度使用,这会使调试和代码易读性变得很麻烦。 Codebases that rely heavily on JQ, for instance, can be a serious PITA to read and debug if you don't encapsulate the near-inevitable anon-heavy and massively overloaded args of the $ soup in a sensible way.例如,严重依赖 JQ 的代码库可能是一个严重的 PITA 来阅读和调试,如果您不以合理的方式封装 $ 汤的几乎不可避免的匿名和大量重载的参数。 JQuery's hover method for instance, is a classic example of over-use of anon funcs when you drop two anon funcs into it, since it's easy for a first-timer to assume it's a standard event listener assignment method rather than one method overloaded to assign handlers for one or two events.例如,JQuery 的悬停方法是当您将两个匿名函数放入其中时过度使用匿名函数的经典示例,因为初学者很容易假设它是标准事件侦听器分配方法而不是重载分配的方法一两个事件的处理程序。 $(this).hover(onMouseOver, onMouseOut) is a lot more clear than two anon funcs. $(this).hover(onMouseOver, onMouseOut)比两个匿名函数要清晰得多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM