对于 JavaScript 事件代码中的回调和参数,使用匿名函数而不是命名函数有什么好处?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10273185/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are the benefits to using anonymous functions instead of named functions for callbacks and parameters in JavaScript event code?
提问by Doug Stephen
I'm new-ish to 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.
我是 JavaScript 新手。我了解该语言的许多概念,我一直在阅读原型继承模型,并且我正在用越来越多的交互式前端内容来吹口哨。这是一种有趣的语言,但我总是对许多非平凡交互模型中典型的回调意大利面有点反感。
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. I'm a Java programmer by day, and discarding the stereotypical jabs about Enterprise-y names for units of codeone 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. So why not use the same approach when writing JavaScript code?
我一直觉得很奇怪的是,尽管 JavaScript 嵌套回调嵌套的可读性噩梦,但我在许多示例和教程中很少看到的一件事是使用预定义的命名函数作为回调参数。白天,我是一名 Java 程序员,摒弃了关于代码单元的 Enterprise-y 名称的刻板印象,我开始享受使用具有大量功能 IDE 选择的语言工作的一件事是使用有意义的,如果很长,名称可以使代码的意图和含义更加清晰,而不会增加实际工作效率。那么为什么在编写 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? 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.
- Quicker dispatching? Not sure about this one either. Guessing again.
- 灵活性。具有回调参数的异步函数可以通过许多不同的代码路径之一访问,并且必须编写命名函数来解释每个可能的边缘情况可能会很麻烦。
- 速度。它在很大程度上影响了黑客的心态。把东西固定在上面,直到它起作用为止。
- 其他人都在做
- 较小的文件大小,即使是微不足道的,但每一点都对网络很重要。
- 更简单的 AST?我假设匿名函数是在运行时生成的,因此 JIT 不会将名称映射到指令,但我现在只是猜测。
- 发货更快?也不确定这个。又猜了。
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. 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.
有这么多现代浏览器显示出比以前更快地执行 JavaScript 代码的能力,我看不出使用匿名回调来获得任何微不足道的性能提升是多么必要。似乎,如果您处于使用命名函数可行的情况(可预测的行为和执行路径),那么就没有理由不这样做。
So are there any technical reasons or gotchas that I'm not aware of that makes this practice so commonplace for a reason?
那么是否有任何我不知道的技术原因或陷阱使这种做法如此普遍是有原因的?
采纳答案by jfriend00
I use anonymous functions for three reasons:
我使用匿名函数有以下三个原因:
- 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.
- 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.
- 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. 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.
我想我会补充一点,如果回调开始超过大约 15-20 行,并且不需要直接访问父作用域中的变量,我会很想给它一个名称并将其分解为它是在别处声明的自己命名的函数。这里肯定有一个可读性点,如果将一个很长的非平凡函数放在自己的命名单元中,它会更易于维护。但是,我最终得到的大多数回调都没有那么长,而且我发现将它们保持内联更具可读性。
回答by Andrew Ensley
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. Say the function is called 8 times throughout the code, that's 153 extra bytesyour code didn't need to have. 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.
第二个确切地告诉您它在名称中的作用。第一个比较模糊。但是,名称有 17 个字符的差异。假设该函数在整个代码中被调用了 8 次,这就是您的代码不需要的153 个额外字节。不是很大,但如果这是一种习惯,将其推断为 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.
然而,同样需要在可维护性与性能优势之间进行权衡。这是处理脚本语言的痛苦。
回答by RickS
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. E.g., "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. (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!
Anon funcs 对于调试器也是匿名的。(废话!)因此,调试器堆栈跟踪通常只会显示一个问号或类似的东西,当您设置多个断点时,它会变得不那么有用。您遇到了断点,但发现自己向上/向下滚动调试窗口以找出您在程序中的位置,因为嘿,问号功能就是不这样做!
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. E.g., at the console command line, do "myFooApp.happyFunc(42)". This is an extremely powerful ability that does not exist (natively) in compiled programming languages. Try that with an anon func.
在全局命名空间中可用的函数,或者像上面一样作为根对象中的节点,可以在开发和调试期间直接从调试器调用。例如,在控制台命令行,执行“myFooApp.happyFunc(42)”。这是一种极其强大的能力,在编译的编程语言中(原生)是不存在的。用 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). E.g.: var funky = function ( ... ) { ... }; jQuery('#otis').click(funky);
通过将匿名函数分配给 var,然后将 var 作为回调(而不是内联)传递,可以使它们更具可读性。例如: var funky = function ( ... ) { ... }; 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.
使用上述方法,您可以潜在地将几个匿名函数分组在父函数的顶部,然后在其下方,顺序语句的内容变得更加紧密,并且更易于阅读。
回答by Jason Sebring
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'));
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 ???? you pick.
你好,我的名字是 Jason 或者你好,我的名字是 ???? 你选。
回答by Erik Reppen
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.
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. It lets me use the same methods internally without the 'this.' cruft and makes order of definition a non-concern when they call each other.
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. 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.
它可以减少一些麻烦,特别是在递归函数中(你可以(实际上应该因为 arguments.callee 已被弃用)仍然在内部使用最后一个示例的命名引用),并明确该函数只在这个函数中触发地方。
代码易读性获胜:在将匿名函数分配为方法的对象字面量的示例中,当该对象字面量的全部目的是在其中添加一些相关功能时,在代码中添加更多位置来寻找和啄食逻辑是愚蠢的同一个方便引用的地方。然而,在构造函数中声明公共方法时,我确实倾向于定义内联标记函数,然后将其分配为 this.sameFuncName 的引用。它让我在内部使用相同的方法而无需“this”。cruft 并在他们互相调用时使定义的顺序变得无关紧要。
对于避免不必要的全局命名空间污染很有用 - 但是,内部命名空间不应该被多个团队同时广泛填充或处理,因此这个论点对我来说似乎有点愚蠢。
在设置短事件处理程序时,我同意内联回调。必须寻找 1-5 行函数是愚蠢的,尤其是因为使用 JS 和函数提升,定义可能会在任何地方结束,甚至不在同一个文件中。这可能是偶然发生的,不会破坏任何东西,不,你并不总是可以控制那些东西。事件总是导致回调函数被触发。没有理由添加更多链接到您需要扫描的名称链,只是为了在大型代码库中对简单的事件处理程序进行逆向工程,并且可以通过将事件触发器自身抽象为在调试时记录有用信息的方法来解决堆栈跟踪问题模式打开并触发触发器。我实际上开始以这种方式构建整个界面。
当您希望函数定义的顺序很重要时很有用。有时您想确定一个默认函数是您认为的那样,直到代码中的某个点可以重新定义它。或者您希望在依赖项被改组时破坏更加明显。
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. 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. 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.
$(this).hover(onMouseOver, onMouseOut)
is a lot more clear than two anon funcs.
匿名函数不能利用函数提升。这是一个主要区别。我倾向于充分利用提升来在底部定义我自己的显式命名的函数和对象构造函数,并在顶部获得对象定义和主循环类型的东西。我发现当你为你的变量命名时,它会让代码更容易阅读,并且只有当它们对你很重要时才能在 ctrl-Fing 之前对发生的事情有一个广泛的了解。在高度事件驱动的接口中,提升也可以是一个巨大的好处,在这种接口中,对可用的东西施加严格的顺序可能会让你大吃一惊。提升有它自己的警告(如循环引用潜力),但它是一个非常有用的工具,用于组织和使代码在正确使用时清晰易读。
易读性/调试。当然,它们有时会被过度使用,这会使调试和代码易读性变得很麻烦。例如,严重依赖 JQ 的代码库可能是一个严重的 PITA 来阅读和调试,如果您不以合理的方式封装 $ 汤的几乎不可避免的匿名和大量重载的参数。例如,JQuery 的悬停方法是当您将两个匿名函数放入其中时过度使用匿名函数的经典示例,因为初学者很容易假设它是标准事件侦听器分配方法而不是重载分配的方法一两个事件的处理程序。
$(this).hover(onMouseOver, onMouseOut)
比两个匿名函数要清楚得多。