JSlint错误'不要在循环中创建函数。' leads to question about Javascript
我有一些代码在循环内调用匿名函数,就像这个伪示例:
for (i = 0; i < numCards; i = i + 1) {
card = $('<div>').bind('isPopulated', function (ev) {
var card = $(ev.currentTarget);
....
JSLint报告错误“不要在循环中创建函数”。 我喜欢保持我的代码JSLint清洁。 我知道我可以将匿名函数移出循环,并将其作为命名函数调用。 除此之外,这是我的问题:
Javascript解释器是否真的会在每次迭代中创建一个函数的实例? 或者真的只有一个函数实例“编译”并且相同的代码被重复执行? 也就是说,JSLint的“建议”是否将函数移出循环实际上会影响代码的效率?
Javascript解释器是否真的会在每次迭代中创建一个函数的实例?
它必须因为它不知道函数对象是否会在其他地方被修改。 请记住,函数是标准的JavaScript对象,因此它们可以具有任何其他对象的属性。 当你这样做时:
card = $('<div>').bind('isPopulated', function (ev) { ... })
对于你所知道的, bind
可以修改对象,例如:
function bind(str, fn) {
fn.foo = str;
}
很明显,如果函数对象在所有迭代中共享,这将导致错误的行为。
部分取决于你使用的是函数表达式还是函数声明。 它们是不同的东西,它们发生在不同的时间,它们对周围的范围有不同的影响。 那么让我们从区分开始。
一个函数表达式是一个function
产品,您将结果用作右值 - 例如,将结果赋值给变量或属性,或将其作为参数传递给函数等。这些都是函数表达式:
setTimeout(function() { ... }, 1000);
var f = function() { ... };
var named = function bar() { ... };
(不要使用最后一个 - 称为命名函数表达式 - 实现有bug,特别是IE。)
相反,这是一个函数声明:
function bar() { ... }
它是独立的,你不会将结果用作右边的值。
他们之间的两个主要区别:
函数表达式在程序流中遇到的地方进行评估。 当控制进入包含范围(例如,包含函数或全局范围)时,将对声明进行评估。
函数的名称(如果有的话)在函数声明的包含范围中定义。 它不适用于函数表达式(禁止浏览器错误)。
你的匿名函数是函数表达式,所以除非解释器在做优化(这是免费的),它们会在每个循环中重新创建。 因此,如果您认为实现将进行优化,那么您的使用就很好,但将其分解为命名函数还有其他好处,并且 - 重要的是 - 不会花费任何东西。 此外,请参阅casablanca的回答,以获取有关为何解释器可能无法在每次迭代中重新创建函数的情况的说明,具体取决于它检查代码的深度。
更大的问题是如果你在循环中使用函数声明,条件的主体等:
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'... */
}
}
你会得到相同的结果,实现将不可能在每个循环中创建一个新的函数,你可以从函数中获得名称的好处,并且不会丢失任何东西。
解释器实际上可以在每次迭代时创建一个新的函数对象,因为该函数可能是一个需要捕获其外部范围中的任何变量的当前值的闭包。
这就是为什么JSLint
想要让你远离在紧密循环中创建许多匿名函数的原因。
上一篇: JSlint error 'Don't make functions within a loop.' leads to question about Javascript itself