JSlint错误'不要在循环中创建函数'。引出有关Javascript本身的问题

时间:2010-10-13 18:59:13

标签: javascript jslint

我有一些代码在循环中调用匿名函数,类似于这个伪示例:

for (i = 0; i < numCards; i = i + 1) {
    card = $('<div>').bind('isPopulated', function (ev) {
        var card = $(ev.currentTarget);
        ....

JSLint报告错误“不要在循环中创建函数”。我喜欢保持我的代码JSLint干净。我知道我可以将匿名函数移出循环并将其作为命名函数调用。除此之外,这是我的问题:

Javascript解释器是否真的会在每次迭代中创建一个函数实例?或者只有一个函数实例“已编译”并且重复执行相同的代码?也就是说,将函数移出循环的JSLint“建议”实际上是否会影响代码的效率?

4 个答案:

答案 0 :(得分:42)

部分取决于您使用的是函数表达式还是函数声明。它们是不同的东西,它们发生在不同的时间,它们对周围的范围产生不同的影响。让我们从区别开始吧。

函数表达式是一个function产品,您将结果用作右手值 - 例如,您将结果分配给变量或属性,或者将它作为参数等传递给函数。这些都是函数表达式

setTimeout(function() { ... }, 1000);

var f = function() {  ... };

var named = function bar() { ... };

(不要使用最后一个 - 这称为命名函数表达式 - 实现有错误,particularly IE。)

相反,这是一个函数声明

function bar() { ... }

它是独立的,你没有将结果用作右手值。

他们之间的两个主要区别:

  1. 评估函数表达式在程序流中遇到的位置。当控件进入包含范围(例如,包含函数或全局范围)时,将评估声明。

  2. 函数的名称(如果有的话)在函数声明的包含范围内定义。它不适用于函数表达式(禁止浏览器错误)。

  3. 您的匿名函数是函数表达式,因此禁止解释器进行优化(它可以自由执行),它们将在每个循环中重新创建。因此,如果您认为实现会优化,那么您的使用就可以了,但将其分解为命名函数还有其他好处,而且 - 重要的是 - 不会花费任何成本。另请参阅casablanca's answer,了解解释器可能无法能够优化每次迭代重新创建函数的原因,具体取决于检查代码的程度。

    更大的问题是如果你在循环中使用函数声明,条件的主体等等:

    function foo() {
        for (i = 0; i < limit; ++i) {
            function bar() { ... } // <== Don't do this
            bar();
        }
    }
    

    从技术上讲,仔细阅读规范的语法表明,无效可以做到这一点,尽管实际上几乎没有实现强制执行。 的实现是多种多样的,最好远离它。

    对于我的钱,最好的办法是使用单一功能声明,如下所示:

    function foo() {
        for (i = 0; i < limit; ++i) {
            bar();
        }
    
        function bar() {
            /* ...do something, possibly using 'i'... */
        }
    }
    

    你得到相同的结果,实现不可能在每个循环上创建一个新函数,你得到benefit of the function having a name,并且你不会丢失任何东西。

答案 1 :(得分:24)

  

Javascript解释器是否真的会为每次迭代创建一个函数实例?

必须这样做,因为它不知道函数对象是否会在别处修改。请记住,函数是标准JavaScript对象,因此它们可以具有与任何其他对象类似的属性。当你这样做时:

card = $('<div>').bind('isPopulated', function (ev) { ... })

如你所知,bind可以修改对象,例如:

function bind(str, fn) {
  fn.foo = str;
}

显然,如果在所有迭代中共享函数对象,这将导致错误的行为。

答案 2 :(得分:2)

解释器实际上可能会在每次迭代时创建一个新的函数对象,只是因为该函数可能是一个需要捕获其外部作用域中任何变量的当前值的闭包。

这就是JSLint想要让你远离在紧密循环中创建许多匿名函数的原因。

答案 3 :(得分:2)

Boo to JSLint。这就像头上的钝器一样。每次遇到function时都会创建一个新的函数对象(它是一个语句/表达式,而不是声明 - 编辑:这是一个白色谎言。请参阅T.J.Crowders答案)。通常这是在闭包等循环中完成的。更大的问题是创建 false闭包

例如:

for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    alert(i)
  }, 10)
}

会导致“奇怪”行为。这不是“在循环中创建函数而不是理解JS用于变量作用域和闭包的规则(变量没有绑定在闭包,作用域 - 执行上下文中)的问题。

但是,您可能希望在函数中创建闭包。考虑这个不太令人惊讶的代码:

for (var i = 0; i < 10; i++) {
  setTimeout((function (_i) { 
    return function () {
      alert(_i)
    }
  })(i), 10)
}

哦不!我还是创建了一个函数!