93

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

第一个更整洁,因为它不会使您的代码与很少使用的函数混淆,但是您多次重新声明该函数是否重要?

4

12 回答 12

93

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

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 对它们的处理确实略有不同。要了解差异,我建议阅读“<a href="http://www.dustindiaz.com/javascript-function-declaration-ambiguity/" rel="noreferrer">JavaScript 函数声明歧义”。

任何方法的实际执行时间很大程度上取决于浏览器对编译器和运行时的实现。有关现代浏览器性能的完整比较,请访问JS Perf 站点

于 2008-09-17T09:07:24.707 回答
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中的同一个函数并且每次都编译它。

于 2008-09-17T07:52:48.410 回答
3

作为一般设计原则,您应该避免多次实现相同的代码。相反,您应该将通用代码提取到一个函数中,并从多个位置执行该(通用、经过良好测试、易于修改)函数。

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

它在特定情况下是一个非常有用的功能,但不应该在很多情况下使用。

于 2008-09-17T07:33:48.143 回答
1

我预计不会有太大差异,但如果有的话,它可能会因脚本引擎或浏览器而异。

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

于 2008-09-17T07:36:49.900 回答
1

我们可以对性能产生影响的地方是声明函数的操作。这是在另一个函数的上下文中或外部声明函数的基准:

http://jsperf.com/function-context-benchmark

在 Chrome 中,如果我们在外部声明该函数,操作会更快,但在 Firefox 中则相反。

在其他示例中,我们看到如果内部函数不是纯函数,它在 Firefox 中也会缺乏性能:http: //jsperf.com/function-context-benchmark-3

于 2015-11-24T21:29:10.153 回答
0

在各种浏览器,尤其是 IE 浏览器中,肯定会让你的循环更快的原因是循环如下:

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

您已经将任意 1000 放入循环条件中,但是如果您想遍历数组中的所有项目,您就会明白我的意思。

于 2008-09-17T07:44:41.270 回答
0

@尼克夫

不过,这是一个相当愚蠢的测试,您正在比较执行和编译时间,这显然会花费方法 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");
}
于 2008-09-17T08:40:08.093 回答
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 语法并没有让任何人失望)

于 2008-09-17T09:11:32.603 回答
0

是的!匿名函数比常规函数更快。也许如果速度是最重要的......比代码重用更重要,然后考虑使用匿名函数。

这里有一篇关于优化 javascript 和匿名函数的非常好的文章:

http://dev.opera.com/articles/view/efficient-javascript/?page=2

于 2008-09-17T09:32:45.363 回答
0

@尼克夫

(希望我有代表发表评论,但我才刚刚找到这个网站)

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

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

于 2008-09-17T10:14:49.147 回答
0

匿名对象比命名对象快。但是调用更多函数的成本更高,并且在某种程度上使您从使用匿名函数中获得的任何节省都黯然失色。每个被调用的函数都会添加到调用堆栈中,这会引入少量但不重要的开销。

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

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

于 2008-09-17T14:15:10.440 回答
0

正如@nickf answer的评论中指出的那样:答案

创建一个函数比创建一个函数快一百万次

是的。但正如他的 JS 性能所显示的那样,它并没有慢一百万倍,这表明它实际上会随着时间的推移而变得更快。

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

重复创建 + 运行与创建一次 + 重复运行相比如何。

如果一个函数执行复杂的计算,那么创建函数对象的时间很可能可以忽略不计。但是在运行速度很快的情况下, 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) { ... }.

于 2017-06-17T09:21:50.180 回答