JSlint错误“不要在循环中创build函数”。 导致关于Javascript本身的问题

我有一些代码在循环内调用匿名函数,像这样的伪示例:

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

JSLint报告错误“不要在一个循环内生成函数”。 我喜欢保持我的代码JSLint干净。 我知道我可以将匿名函数移出循环,并将其作为命名函数调用。 这一边,这是我的问题:

一个Javascript解释器真的会创build每个迭代函数的一个实例吗? 还是真的只有一个函数实例“编译”,并重复执行相同的代码? 也就是说,JSLint的“build议”是否将该function移出循环实际上影响了代码的效率?

一个Javascript解释器真的会创build每个迭代函数的一个实例吗?

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

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

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

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

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

部分取决于你使用的是函数expression式还是函数声明 。 他们是不同的东西,他们在不同的时间发生,他们对周围的范围有不同的影响。 那么让我们从这个区别开始。

一个函数expression式是一个function产品,您将结果用作右值 – 例如,将结果赋值给variables或属性,或者将其作为parameter passing给函数等。函数expression式

 setTimeout(function() { ... }, 1000); var f = function() { ... }; var named = function bar() { ... }; 

(不要使用最后一个 – 这被称为命名函数expression式 – 实现有bug, 特别是IE 。)

相反,这是一个函数声明

 function bar() { ... } 

它是独立的,你不会将结果用作右边的值。

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

  1. 函数expression式在程序stream中遇到的地方被评估。 当控制进入包含范围(例如,包含函数或全局范围)时,声明被评估。

  2. 函数的名称(如果有的话)在函数声明的包含范围中定义。 这不是一个函数expression式 (禁止浏览器错误)。

你的匿名函数是函数expression式 ,所以禁止解释器进行优化(这是免费的),它们将在每个循环中被重新创build。 因此,如果您认为实现将会优化,那么您的使用就没有问题,但是将其分解为命名函数还有其他好处,而且重要的是不会花费任何东西。 此外,请参阅casablanca的回答,以获得有关为何解释器可能无法在每次迭代中重新创build函数的情况的说明,具体取决于它检查代码的深度。

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

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

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

对于我的钱,最好的办法是使用一个函数声明,像这样:

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

你得到了同样的结果,实现将不可能在每一个循环中创build一个新的函数,你得到的函数有一个名字的好处 ,你不会失去任何东西。

解释器实际上可以在每次迭代时创build一个新的函数对象,只是因为该函数可能是一个需要捕获其外部范围中的任何variables的当前值的闭包。

这就是为什么JSLint想要吓跑你在严密的循环中创build许多匿名函数。

Boo给JSLint。 这就像头部钝器。 每次遇到函数时都会创build一个新的函数对象(这是一个声明/expression式,而不是声明 – 编辑:这是一个白色的谎言,请参阅TJ Crowders答案 )。 通常这是在一个封闭循环中完成的,等等。更大的问题是创build错误的closures

例如:

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

会导致“奇怪”的行为。 这不是一个“在循环中创build一个函数,不理解JS用于variables作用域和闭包(variables不在闭包,作用域 – 执行上下文中)的规则”的问题。

但是,您可能需要在函数中创build闭包。 考虑这个不太令人吃惊的代码:

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

不好了! 我还是创build了一个函数!