使用匿名函数会影响性能吗?


89

我一直想知道,在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;
}

第一个是比较整洁的,因为它不会因很少使用的函数而使您的代码混乱,但是多次重声明该函数是否重要呢?


我知道这不是问题所在,但是关于代码清洁/易读性,我认为“正确的方法”位于中间。很少使用的顶级函数的“混乱”令人讨厌,但嵌套过多的代码也是如此,它们很大程度上依赖于在调用时与之内联声明的匿名函数(请考虑使用node.js回调地狱)。前者和后者都会使调试/执行跟踪变得困难。
Zac B

下面的性能测试对函数运行了数千次迭代。即使您看到了很大的差异,大多数用例也不会按该顺序进行迭代。因此,最好选择适合您需求的任何东西,而忽略这种特殊情况下的性能。
用户

@nickf当然是个过时的问题,但请参阅新的更新答案
Chandan Pasunoori 2014年

Answers:


89

这里的性能问题是在循环的每次迭代中创建新函数对象的成本,而不是您使用匿名函数的事实:

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网站。


您忘记了函数正文之前的括号。我刚刚测试过,这是必需的。
Chinoto Vokro 2014年

看来基准测试结果很大程度上取决于js引擎!
aleclofabbro 2014年

3
JS Perf示例中是否没有缺陷:情况1仅定义了函数,而情况2和3似乎无意中调用了该函数。
bluenote10

因此,使用这种推理方法是否意味着在开发node.jsWeb应用程序时,比创建匿名回调好于在请求流之外创建函数并将它们作为回调传递吗?
Xavier T Mukodi

23

这是我的测试代码:

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中的功能相同,因此每次都对其进行编译。


3
该测试在哪个浏览器中进行?
andynil 2010年

5
我在Chrome 23上的时间:(2ms / 17ms),IE9:(20ms / 83ms),FF 17:(2ms / 96ms)
Davy8 2012年

您的答案值得更多的重视。我在Intel i5 4570S上的时间:Chrome 41(1/9),IE11(1/25),FF36(1/14)。显然,循环中的匿名函数的性能较差。
克拉克

3
该测试没有看起来有用。在这两个示例中,都没有实际执行内部功能。实际上,所有测试表明,创建函数10000000次比创建函数一次要快。
Nucleon

2

作为一般设计原则,应避免多次隐含相同的代码。相反,您应该将通用代码放入函数中,然后从多个位置执行该函数(通用,经过良好测试,易于修改)。

如果(不同于您从问题中推断出的内容)是一次声明了内部函数,并且一次使用了该代码(并且程序中没有其他相同的东西),那么一个匿名函数可能(多数民众赞成在猜测中)被相同的方式对待。编译器作为普通的命名函数。

它在特定情况下非常有用,但在许多情况下不应该使用。


1

我希望不会有太大的区别,但是如果有区别的话,脚本引擎或浏览器可能会有所不同。

如果您发现该代码更容易理解,那么除非您希望调用该函数数百万次,否则性能就不是问题。



0

绝对可以使您在各种浏览器(尤其是IE浏览器)中循环更快的方法如下:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

您已将任意1000放入循环条件中,但是如果您想遍历数组中的所有项目,则会感到不安。


0

引用几乎总是比引用的对象慢。这样想吧-假设您要打印加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语法没有使任何人失望)


0

@尼克

(希望我的代表只发表评论,但我只是找到了这个网站)

我的观点是,命名/匿名函数与在迭代中执行+编译的用例之间存在混淆。正如我所说明的,匿名+命名之间的区别本身可以忽略不计-我是说用例是有问题的。

对我来说似乎很明显,但是如果不是这样,我认为最好的建议是“不要做愚蠢的事情”(其中不停地进行块移位+此用例的对象创建就是其中之一),如果不确定,请进行测试!



0

匿名对象比命名对象快。但是,调用更多函数会更加昂贵,并且在某种程度上可以抵消使用匿名函数可能节省的费用。每个被调用的函数都会添加到调用堆栈中,这会带来少量但不小的开销。

但是,除非您正在编写加密/解密例程或类似的对性能敏感的程序,否则,正如许多其他人所指出的那样,与快速代码相比,针对优雅,易于阅读的代码进行优化总是更好的选择。

假设您正在编写结构良好的代码,那么编写解释器/编译器的人员应该负责速度问题。


0

@尼克

不过,这是一个相当致命的测试,您要在此处比较执行和编译时间,这显然会使方法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");
}

0

正如@nickf的评论中所指出的答案:

创建函数的速度比创建函数的速度快一百万次

是肯定的。但是,正如他的JS perf所显示的那样,它的速度并没有降低一百万倍,这表明它实际上随着时间的推移而变得更快。

对我来说更有趣的问题是:

如何做一个重复创建+运行比较创建一次+反复运行

如果函数执行复杂的计算,则创建函数对象的时间极有可能被忽略。但是在运行速度快的情况下,create的开销又如何呢?例如:

// 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) { ... }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.