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

时间:2008-09-17 07:28:45

标签: javascript performance optimization

我一直在想,在Javascript中使用命名函数和匿名函数之间是否存在性能差异?

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

VS

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

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

12 个答案:

答案 0 :(得分:81)

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

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

您正在创建一千个不同的函数对象,即使它们具有相同的代码体并且没有绑定到词法范围(closure)。另一方面,以下似乎更快,因为它只是在整个循环中将相同的函数引用分配给数组元素:

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 function declaration ambiguity”。

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

答案 1 :(得分:21)

这是我的测试代码:

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中的功能是相同的,并且每次都会编译它。

答案 2 :(得分:2)

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

如果(与您从问题中推断出的不同),您宣布内部函数一次并使用该代码一次(并且在您的程序中没有其他任何内容),那么一个自治函数可能(这是一个猜测人们会被编译器以与普通命名函数相同的方式对待。

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

答案 3 :(得分:1)

我不希望有太大差异,但如果有一个,它可能会因脚本引擎或浏览器而异。

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

答案 4 :(得分:1)

匿名对象比命名对象更快。但是调用更多函数会更加昂贵,并且在某种程度上会使您使用匿名函数所获得的任何节省都黯然失色。每个被调用的函数都会添加到调用堆栈中,这会引入一些小但非常重要的开销。

但是,除非您正在编写加密/解密例程或类似的对性能敏感的事情,否则许多其他人已经注意到,优化优雅,易于阅读的代码优于快速代码。

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

答案 5 :(得分:1)

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

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

在Chrome中,如果我们在外面声明这个功能,操作会更快,但在Firefox中则相反。

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

答案 6 :(得分:0)

在各种浏览器(尤其是IE浏览器)中,您的循环速度肯定会更快地循环,如下所示:

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

你已经在循环条件中输入了任意1000,但是如果你想要遍历数组中的所有项目,你会得到我的漂移。

答案 7 :(得分:0)

@nickf

这是一个相当愚蠢的测试,你要比较那里的执行和编译时间,这显然要花费方法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");
}

答案 8 :(得分: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语法没有抛弃任何人)

答案 9 :(得分:0)

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

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

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

答案 10 :(得分:0)

@nickf

(希望我让代表只是发表评论,但我只是发现了这个网站)

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

对我来说似乎很明显,但如果不是,我认为最好的建议是“不做蠢事”(其中常量块移位+此用例的对象创建是一个)如果你不确定,测试!

答案 11 :(得分:0)

正如对@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 Perf表示只创建一次函数的速度比预期的要快。但是,即使像简单添加一样快速操作,重复创建函数的开销也只有几个百分点。

这种差异可能只会在创建函数对象很复杂的情况下变得很重要,同时保持可忽略的运行时间,例如,如果整个函数体被包装成if (unlikelyCondition) { ... }