使用匿名函数会影响性能吗?
-
09-06-2019 - |
题
我一直想知道,在 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;
}
第一个更整洁,因为它不会用很少使用的函数使您的代码变得混乱,但是多次重新声明该函数有什么关系吗?
解决方案
这里的性能问题是在循环的每次迭代中创建新函数对象的成本,而不是使用匿名函数的事实:
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 网站
其他提示
这是我的测试代码:
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:1983毫秒
看来 JS 引擎无法识别 Test2 中的相同函数,并且每次都会编译它。
作为一般设计原则,您应该避免多次实现相同的代码。相反,您应该将通用代码提取到一个函数中,并从多个位置执行该函数(通用的、经过良好测试的、易于修改的)。
如果(与您从问题中推断出的不同)您声明内部函数一次并使用该代码一次(并且程序中没有其他相同的内容),那么这是一个匿名函数 大概 (这是人们的猜测)编译器以与普通命名函数相同的方式对待。
它在特定情况下是一个非常有用的功能,但在许多情况下不应该使用。
我预计不会有太大差异,但如果有的话,它可能会因脚本引擎或浏览器而异。
如果您发现代码更容易理解,那么性能就不是问题,除非您希望调用该函数数百万次。
匿名对象比命名对象更快。但是调用更多函数的成本更高,并且在一定程度上超过了使用匿名函数可能获得的节省。每个调用的函数都会添加到调用堆栈中,这会带来少量但不小的开销。
但是,除非您正在编写加密/解密例程或类似对性能敏感的内容,否则正如许多其他人指出的那样,优化优雅、易于阅读的代码总是比快速代码更好。
假设您正在编写架构良好的代码,那么速度问题应该由编写解释器/编译器的人负责。
我们可以对性能产生影响的地方是声明函数的操作。这是在另一个函数的上下文内或外部声明函数的基准:
http://jsperf.com/function-context-benchmark
在 Chrome 中,如果我们在外部声明函数,操作会更快,但在 Firefox 中则相反。
在其他示例中,我们看到如果内部函数不是纯函数,它在 Firefox 中也会缺乏性能:http://jsperf.com/function-context-benchmark-3
肯定会让你的循环在各种浏览器(尤其是 IE 浏览器)上运行得更快,循环如下:
for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
// do something
}
您已将任意 1000 输入到循环条件中,但如果您想遍历数组中的所有项目,您就会明白我的意思。
引用几乎总是比它所引用的对象慢。可以这样想 - 假设您要打印 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 语法并没有让任何人失望)
@尼克夫
(希望我有代表发表评论,但我才刚刚找到这个网站)
我的观点是,命名/匿名函数和迭代中执行+编译的用例之间存在混淆。正如我所说明的, anon+named 之间的差异本身可以忽略不计 - 我是说这是错误的用例。
这对我来说似乎是显而易见的,但如果不是,我认为最好的建议是“不要做愚蠢的事情”(其中这个用例的不断块移动+对象创建就是其中之一),如果您不确定,请测试!
是的!匿名函数比常规函数更快。也许如果速度是最重要的......比代码重用更重要的是考虑使用匿名函数。
这里有一篇关于优化 javascript 和匿名函数的非常好的文章:
http://dev.opera.com/articles/view/efficient-javascript/?page=2
@尼克夫
但这是一个相当愚蠢的测试,你正在比较执行情况 和编译 时间,这显然会消耗方法 1(编译 N 次,取决于 JS 引擎)和方法 2(编译一次)。我无法想象一个 JS 开发人员会通过试用期以这种方式编写代码。
更现实的方法是匿名分配,因为实际上您用于 document.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");
}
正如 @nickf 回答的评论中指出的:答案是
创建一个函数一次比创建它一百万次还要快
是的。但正如他的 JS 性能所示,它并没有慢一百万倍,这表明它实际上随着时间的推移而变得更快。
对我来说更有趣的问题是:
重复怎么办 创建+运行 比较创建一次+重复 跑步.
如果函数执行复杂的计算,则创建函数对象的时间很可能可以忽略不计。但是头顶上的东西又如何呢? 创造 在情况下 跑步 快吗?例如:
// 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 性能 表明仅创建一次该函数比预期更快。然而,即使使用像简单添加这样非常快速的操作,重复创建函数的开销也只有百分之几。
这种差异可能只在创建函数对象很复杂而运行时间可以忽略不计的情况下才变得显着,例如,如果整个函数体被包装到一个 if (unlikelyCondition) { ... }
.