【问题标题】:Does use of anonymous functions affect performance?使用匿名函数会影响性能吗?
【发布时间】:2010-09-09 23:54:58
【问题描述】:

我一直在想,在 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 回调地狱)。前者和后者都可能使调试/执行跟踪变得困难。
  • 下面的性能测试会运行该函数进行数千次迭代。即使您看到了实质性差异,大多数用例也不会按照该顺序的迭代执行此操作。因此,最好选择适合您需要的任何内容,而忽略此特定情况下的性能。
  • @nickf 当然它的问题太老了,但请查看新的更新答案

标签: javascript performance optimization


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

        这是我的测试代码:

        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));
        }
        

        结果:
        测试一:142ms 测试 2:1983 毫秒

        好像JS引擎没有识别出Test2中的同一个函数,每次都编译。

        【讨论】:

        • 此测试是在哪个浏览器中进行的?
        • 我在 Chrome 23 上的时间:(2ms/17ms),IE9:(20ms/83ms),FF 17:(2ms/96ms)
        • 您的回答值得更多重视。我在 Intel i5 4570S 上的时间:Chrome 41 (1/9)、IE11 (1/25)、FF36 (1/14)。显然,循环中的匿名函数性能更差。
        • 这个测试并不像看起来那么有用。在这两个示例中,内部功能都没有实际执行。实际上,所有这个测试都表明,创建一个函数 10000000 次比创建一个函数一次要快。
        • 这个测试表明引擎无法识别新创建的函数总是相同的。但到 2021 年差异会更小。
        【解决方案5】:

        @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");
        }
        

        【讨论】:

          【解决方案6】:

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

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

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

          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

          【讨论】:

          • 您忘记了函数体前的括号。我刚刚测试过,它们是必需的。
          • 看来基准测试结果非常依赖 js-engine!
          • JS Perf 示例中没有缺陷吗:案例 1 仅定义函数,而案例 2 & 3 似乎不小心调用函数。
          • 那么使用这个推理,是不是说在开发node.js web 应用程序时,最好在请求流之外创建函数,并将它们作为回调传递,而不是创建匿名回调?跨度>
          【解决方案7】:

          引用几乎总是会比它所引用的东西慢。这样想 - 假设你想打印加 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 语法并没有让任何人失望)

          【讨论】:

            【解决方案8】:

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

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

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

            【讨论】:

              【解决方案9】:

              @nickf

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

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

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

              【讨论】:

                【解决方案10】:

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

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

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

                【讨论】:

                  【解决方案11】:

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

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

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

                  在另一个例子中,我们看到如果内部函数不是纯函数,它在 Firefox 中也将缺乏性能: http://jsperf.com/function-context-benchmark-3

                  【讨论】:

                    【解决方案12】:

                    正如@nickf 回答的 cmets 中指出的那样:

                    的答案

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

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

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

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

                    如果函数执行复杂的计算,创建函数对象的时间很可能可以忽略不计。但是在 run 很快的情况下,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 表明只创建一次函数会比预期的更快。然而,即使是像简单的 add 这样非常快速的操作,重复创建函数的开销也只有百分之几。

                    只有在创建函数对象很复杂,同时保持可忽略不计的运行时间(例如,如果将整个函数体包装到 if (unlikelyCondition) { ... } 中)时,差异可能才会变得显着。

                    【讨论】:

                      猜你喜欢
                      • 2020-05-30
                      • 1970-01-01
                      • 2014-08-22
                      • 2010-09-26
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-05-21
                      • 2011-07-07
                      • 2014-11-10
                      相关资源
                      最近更新 更多