使用匿名函数会影响性能吗?
我一直想知道,在Javascript中使用命名函数和匿名函数之间是否存在性能差异?
for (var i = 0; i < 1000; ++i) { myObjects[i].onMyEvent = function() { // do something }; }
与
function myEventHandler() { // do something } for (var i = 0; i < 1000; ++i) { myObjects[i].onMyEvent = myEventHandler; }
第一个是比较整洁的,因为它不会因很少使用的函数而使代码混乱,但是多次重声明该函数是否重要呢?
解决方案
回答
作为一般设计原则,应避免多次隐含相同的代码。相反,我们应该将通用代码放入函数中,然后从多个位置执行该函数(通用,经过良好测试,易于修改)。
如果(不同于从问题中推断出的内容)是一次声明了内部函数并使用了该代码一次(并且程序中没有其他相同的东西),那么匿名函数(多数民众赞成在猜测中)可能被编译器作为普通的命名函数。
它在特定情况下是非常有用的功能,但在许多情况下不应使用。
回答
我希望不会有太大的差异,但是如果有差异,则脚本引擎或者浏览器可能会有所不同。
如果我们发现该代码更易于理解,那么除非我们希望调用该函数数百万次,否则性能就不是问题。
回答
绝对可以使我们在各种浏览器(尤其是IE浏览器)中循环更快的原因如下:
for (var i = 0, iLength = imgs.length; i < iLength; i++) { // do something }
我们已将任意1000放入循环条件中,但是如果我们想遍历数组中的所有项目,就会感到困惑。
回答
这是我的测试代码:
var dummyVar; function test1() { for (var i = 0; i < 1000000; ++i) { dummyVar = myFunc; } } function test2() { for (var i = 0; i < 1000000; ++i) { dummyVar = function() { var x = 0; x++; }; } } function myFunc() { var x = 0; x++; } document.onclick = function() { var start = new Date(); test1(); var mid = new Date(); test2(); var end = new Date(); alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid)); }
结果:
测试1:142ms
测试2:1983ms
似乎JS引擎无法识别它与Test2中的功能相同,因此每次都对其进行编译。
回答
@尼克
不过,这是一个相当致命的测试,我们要在此处比较执行和编译时间,这显然会使方法1(编译N次,取决于JS引擎)与方法2(编译一次)的成本。我无法想象一个JS开发人员会以这种方式通过试用期来编写代码。
一种更现实的方法是匿名分配,因为实际上我们正在为文档使用。onclick方法更像下面的方法,实际上稍微偏爱anon方法。
使用与我们类似的测试框架:
function test(m) { for (var i = 0; i < 1000000; ++i) { m(); } } function named() {var x = 0; x++;} var test1 = named; var test2 = function() {var x = 0; x++;} document.onclick = function() { var start = new Date(); test(test1); var mid = new Date(); test(test2); var end = new Date(); alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms"); }
回答
这里的性能问题是在循环的每次迭代中创建新函数对象的成本,而不是我们使用匿名函数的事实:
for (var i = 0; i < 1000; ++i) { myObjects[i].onMyEvent = function() { // do something }; }
即使它们具有相同的代码体并且没有绑定到词法范围(闭包),我们仍在创建上千个不同的函数对象。另一方面,以下代码看起来更快,因为它在整个循环中只是将相同的函数引用分配给数组元素:
function myEventHandler() { // do something } for (var i = 0; i < 1000; ++i) { myObjects[i].onMyEvent = myEventHandler; }
如果要在进入循环之前创建匿名函数,然后仅将其引用分配给循环内部的数组元素,则与命名函数版本相比,我们将发现没有任何性能或者语义差异:
var handler = function() { // do something }; for (var i = 0; i < 1000; ++i) { myObjects[i].onMyEvent = handler; }
简而言之,使用匿名函数而不是命名函数没有明显的性能代价。
顺便说一句,从上方看似乎没有什么区别:
function myEventHandler() { /* ... */ }
和:
var myEventHandler = function() { /* ... */ }
前者是函数声明,而后者是对匿名函数的变量赋值。尽管它们看起来可能具有相同的效果,但是JavaScript确实对它们的处理略有不同。为了理解它们之间的区别,我建议阅读JavaScript函数声明的歧义。
任何方法的实际执行时间在很大程度上将取决于浏览器对编译器和运行时的实现。有关现代浏览器性能的完整比较,请访问JS Perf网站。
回答
引用几乎总是比引用的对象慢。这样想,假设我们要打印加1 + 1的结果。这更有意义:
alert(1 + 1);
或者
a = 1; b = 1; alert(a + b);
我意识到这是一种非常简单的查看方式,但这只是说明性的,对吧?例如,仅在要多次使用引用时才使用引用,这些示例中的哪个更有意义:
$(a.button1).click(function(){alert('you clicked ' + this);}); $(a.button2).click(function(){alert('you clicked ' + this);});
或者
function buttonClickHandler(){alert('you clicked ' + this);} $(a.button1).click(buttonClickHandler); $(a.button2).click(buttonClickHandler);
第二个是更好的做法,即使它有更多行。希望所有这些对我们有所帮助。 (并且jQuery语法没有使任何人失望)
回答
是的!匿名函数比常规函数要快。如果速度是最重要的……比重用代码更重要,那么考虑使用匿名函数。
这里有一篇关于优化javascript和匿名函数的非常好的文章:
http://dev.opera.com/articles/view/efficiency-javascript/?page=2
回答
@尼克
(希望我的代表只发表评论,但我只是找到了这个网站)
我的观点是,命名/匿名函数与在迭代中执行+编译的用例之间存在混淆。正如我所说明的,匿名+命名之间的区别本身可以忽略不计,我是说用例是错误的。
对我来说似乎很明显,但是如果不是,我认为最好的建议是"不要做愚蠢的事情"(其中不断用块移动和此用例创建对象是其中之一),如果不确定,请测试!
回答
匿名对象比命名对象快。但是,调用更多函数会更昂贵,并且在某种程度上可以抵消使用匿名函数可能节省的费用。每个被调用的函数都会添加到调用堆栈中,这会带来少量但不小的开销。
但是,除非我们正在编写加密/解密例程或者类似的对性能敏感的程序,否则,正如许多其他人所指出的那样,与快速代码相比,针对优雅,易于阅读的代码进行优化总是更好的选择。
假设我们正在编写结构良好的代码,那么速度问题应由编写解释器/编译器的人员负责。