我一直想知道,在Javascript中使用命名函数和匿名函数之间是否存在性能差异?
1 2 3 4 5
| for (var i = 0; i < 1000; ++i) {
myObjects[i].onMyEvent = function() {
// do something
};
} |
与
1 2 3 4 5 6 7
| function myEventHandler() {
// do something
}
for (var i = 0; i < 1000; ++i) {
myObjects[i].onMyEvent = myEventHandler;
} |
第一个是比较整洁的,因为它不会因很少使用的函数而使您的代码混乱,但是多次重声明该函数是否重要呢?
这里的性能问题是在循环的每次迭代中创建新的函数对象的成本,而不是您使用匿名函数的事实:
1 2 3 4 5
| for (var i = 0; i < 1000; ++i) {
myObjects[i].onMyEvent = function() {
// do something
};
} |
即使它们具有相同的代码体并且没有绑定到词法范围(闭包),您仍在创建上千个不同的函数对象。另一方面,下面的方法看起来更快,因为它在整个循环中只是将相同的函数引用分配给数组元素:
1 2 3 4 5 6 7
| function myEventHandler() {
// do something
}
for (var i = 0; i < 1000; ++i) {
myObjects[i].onMyEvent = myEventHandler;
} |
如果要在进入循环之前创建匿名函数,然后仅在循环内将对它的引用分配给数组元素,则与命名函数版本相比,您将发现性能或语义上没有任何区别:
1 2 3 4 5 6
| var handler = function() {
// do something
};
for (var i = 0; i < 1000; ++i) {
myObjects[i].onMyEvent = handler;
} |
简而言之,使用匿名函数而不是命名函数不会产生明显的性能代价。
顺便说一句,从上方看似乎没有什么区别:
1
| function myEventHandler() { /* ... */ } |
和:
1
| var myEventHandler = function() { /* ... */ } |
前者是函数声明,而后者是对匿名函数的变量赋值。尽管它们看起来可能具有相同的效果,但是JavaScript确实对它们的处理略有不同。要了解它们之间的区别,建议阅读" JavaScript函数声明的歧义"。
任何方法的实际执行时间在很大程度上将取决于浏览器对编译器和运行时的实现。有关现代浏览器性能的完整比较,请访问JS Perf网站。
这是我的测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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) +"\
Test 2:" + (end - mid));
} |
结果:
测试1:142ms
测试2:1983ms
似乎JS引擎无法识别它与Test2中的功能相同,因此每次都对其进行编译。
作为一般设计原则,应避免多次隐含相同的代码。相反,您应该将通用代码放入一个函数中,然后从多个位置执行该函数(通用,经过良好测试,易于修改)。
如果(不同于您从问题中推断出的内容)是一次声明了内部函数,并且一次使用了该代码(并且程序中没有其他相同的东西),那么一个匿名函数可能(多数民众赞成在猜测中)会被相同的方式对待。编译器作为普通的命名函数。
它在特定情况下非常有用,但在许多情况下不应该使用。
对性能有影响的地方就是声明函数的操作。这是在另一个函数的上下文内或外部声明函数的基准:
http://jsperf.com/function-context-benchmark
在Chrome中,如果我们在外部声明该函数,则操作会更快,但在Firefox中则相反。
在另一个示例中,我们看到,如果内部函数不是纯函数,则在Firefox中也将缺乏性能:
http://jsperf.com/function-context-benchmark-3
匿名对象比命名对象快。但是,调用更多函数会更加昂贵,并且在某种程度上可以弥补使用匿名函数可能节省的费用。每个被调用的函数都会添加到调用堆栈中,这会带来少量但不小的开销。
但是,除非您正在编写加密/解密例程或类似的对性能敏感的程序,否则,正如许多其他人所指出的那样,与快速代码相比,针对优雅,易于阅读的代码进行优化总是更好的选择。
假设您正在编写结构良好的代码,那么编写解释器/编译器的人员应该负责速度问题。
我希望不会有太大的区别,但是如果有区别的话,脚本引擎或浏览器可能会有所不同。
如果您发现该代码更易于理解,那么除非您希望调用该函数数百万次,否则性能就不是问题。
正如@nickf的评论中指出的那样:
Is creating a function once faster than creating it a million times
是肯定的。但是,正如他的JS perf所显示的那样,它的速度并没有降低一百万倍,这表明它实际上随着时间的推移变得越来越快。
对我来说更有趣的问题是:
How does a repeated create + run compare to create once + repeated run.
如果函数执行复杂的计算,则创建函数对象的时间极有可能被忽略。但是在运行速度快的情况下,create的开销又如何呢?例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // Variant 1: create once
function adder(a, b) {
return a + b;
}
for (var i = 0; i < 100000; ++i) {
var x = adder(412, 123);
}
// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
function adder(a, b) {
return a + b;
}
var x = adder(412, 123);
}
// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
var x = (function(a, b) { return a + b; })(412, 123);
} |
此JS Perf显示,仅创建一次函数会比预期的快。但是,即使使用非常快速的操作(例如简单的添加),重复创建函数的开销也仅占百分之几。
仅在创建函数对象很复杂且保持运行时间可忽略的情况下(例如,将整个函数主体包装到if (unlikelyCondition) { ... }中),差异才可能变得很明显。
@尼克
(希望我的代表只发表评论,但我只是找到了这个网站)
我的观点是,命名/匿名函数与在迭代中执行+编译的用例之间存在混淆。正如我所说明的,匿名+命名之间的区别本身可以忽略不计-我是说用例是有问题的。
对我来说似乎很明显,但是如果不是,我认为最好的建议是"不要做愚蠢的事情"(这种用例的恒定块移位和对象创建就是其中之一),如果不确定,请进行测试!
是!匿名函数比常规函数要快。也许如果速度是最重要的……比重用代码更重要,那么可以考虑使用匿名函数。
这里有一篇关于优化javascript和匿名函数的非常好的文章:
http://dev.opera.com/articles/view/efficiency-javascript/?page=2
引用几乎总是比引用的对象慢。这样想吧-假设您要打印加1 + 1的结果。这更有意义:
要么
1 2 3
| a = 1;
b = 1;
alert(a + b); |
我意识到这是一种非常简单的查看方式,但这只是说明性的,对吧?仅在要多次使用引用时才使用它-例如,以下示例中的哪个更有意义:
1 2
| $(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);}); |
要么
1 2 3
| function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler); |
第二个是更好的做法,即使它有更多行。希望所有这些对您有所帮助。 (并且jQuery语法没有使任何人失望)
@尼克
不过,这是一个相当致命的测试,您要在此处比较执行和编译时间,这显然将使方法1(编译N次,取决于JS引擎)与方法2(编译一次)的成本。我无法想象一个JS开发人员会以这种方式通过试用期来编写代码。
一种更现实的方法是匿名分配,因为实际上您正在为文档使用。onclick方法更像下面的方法,实际上稍微偏爱anon方法。
使用与您类似的测试框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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\
Test 2:" + (end - mid) +"ms");
} |
绝对可以使您在各种浏览器(尤其是IE浏览器)中循环更快的原因如下:
1 2 3 4
| for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
// do something
} |
您已将任意1000放入循环条件中,但是如果您想遍历数组中的所有项目,就会感到困惑。