我的一个朋友被所有着名的'循环'javascript问题中的匿名函数所困扰。 (它被解释为死于SO,而我实际上期待有人将我的问题视为副本,这可能是公平的游戏)。
这个问题相当于John Resig在本教程中解释的内容:
http://ejohn.org/apps/learn/#62
var count = 0;
for ( var i = 0; i < 4; i++ ) {
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
}
对于一个新用户来说,它应该有用,但确实“我总是有相同的价值”,他们说,因此哭泣和牙齿咬牙切齿。
我用很多挥手解释了这个问题和一些关于范围的东西,并指出他在SO或其他网站上提供的一些解决方案(实际上,当你知道它是如此常见的问题时,google 是< / em>你的朋友)。
当然,真正的答案是在JS中,范围是在功能级别。因此,当匿名函数运行时,'i'没有在其中任何一个的范围内定义,但它在全局范围内定义,并且它具有循环结束的值,4。
由于我们很可能都接受过使用块级范围的语言的训练,所以它是另一回事 - js-that-a-little-bit-different-the-rest-of-rest-世界(“这个”的意思,任何人?)
让我感到困惑的是,共同的答案,实际上甚至是John自己提供的答案如下:
var count = 0;
for ( var i = 0; i < 4; i++ ) (function(i){
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
})(i);
这显然有效并且证明了对语言的掌握以及对嵌套括号的一种品味,这种情况可疑地让你看起来像一个LISP-er。
然而,我不禁想到其他解决方案 更容易理解,更容易解释。
这个只是推动匿名闭包有点闭合更接近setTimeout(或更接近addEventListener是99.9%的情况下它咬人):
var count = 0;
for (var i = 0 ; i < 4 ; i++) {
setTimeout((function (index) {
return function () {
assert( index == count++, "Check the value of i." );
}
})(i), i*200);
};
这是关于使用显式函数工厂解释我们正在做的事情:
var count = 0
function makeHandler(index) {
return function() {
assert(index == count ++);
};
};
for (var i = 0 ; i < 4 ; i++) {
setTimeout(makeHandler(i), i*200);
};
最后,还有另一个解决方案可以完全消除这个问题,对我来说似乎更自然(尽管我同意它以某种方式避免了'偶然'问题')
var count = 0;
function prepareTimeout(index) {
setTimeout(function () {
assert(index == count++);
}, index * 200);
};
for (var k = 0; k < 4 ; k++) {
prepareTimeout(k);
};
这些解决方案在内存使用情况,创建范围数量,可能泄漏方面完全等效吗?
对不起,如果这真的是常见问题或主观或其他。
答案 0 :(得分:4)
解决方案#1
for (var i = 0; i < 4; i++) (function (i) {
// scope #1
setTimeOut(function () {
// scope #2
})(i), i*200);
})(i);
实际上相当不错,因为它减少了缩进并因此降低了代码复杂性。另一方面,for
循环没有自己的块,这是jsLint(在我看来是合理的)会抱怨的。
解决方案#2
for (var i = 0; i < 4; i++) {
setTimeout((function (i) {
// scope #1
return function () {
// scope #2
}
})(i), i*200);
};
我大部分时间都会这样做。 for循环有一个实际的块,我发现它增加了可读性,并且与你对传统循环的期望更加合理(如“for x do y”,与“for x创建匿名函数并立即执行”来自#1)。但这是在旁观者的眼中,真的,你拥有的经验越多,这些方法开始对你来说就越相同。
解决方案#3
function makeHandler(index) {
// scope #1
return function() {
// scope #2
};
};
for (var i = 0 ; i < 4 ; i++) {
setTimeout(makeHandler(i), i*200);
};
正如你所说的那样,for
循环本身更清晰。人类思维可以更容易地适应命名块,这些块做预定义的事情而不是一堆嵌套的匿名函数。
function prepareTimeout(index) {
// scope #1
setTimeout(function () {
// scope #2
}, index * 200);
};
for (var k = 0; k < 4 ; k++) {
prepareTimeout(k);
};
绝对是一回事。我在这里看不到任何“问题回避”,它只相当于#3。
正如我所看到的,这些方法在最低限度上没有区别,语义 - 只有语法。有时候有理由偏好一个(例如“功能工厂”方法非常可重用),但这并不适用于你描述的标准情况。
无论如何,JavaScript的新用户必须掌握三个概念:
一旦这些概念陷入困境,你就不会再看到这些方法有如此大的差异。他们只是“把它”的不同方式。在那之前,你只需选择一个你最熟悉的那个。
编辑:你可以争辩说,当你从过渡时,你必须像这样“态度转变为”你可以这样做,或者像这样,或者喜欢这种“态度。这适用于编程语言,烹饪和其他任何东西。
要谈谈标题中的隐含问题:可读性也是旁观者的眼睛。问任何Perl程序员。或者对正则表达式感到满意的人。
答案 1 :(得分:0)
在我看来,最后一个模式(使用预定义的命名函数)是最具可读性的“可调试”,并且在我的编辑器中最常用(KomodoEdit或Visual Studio与Resharper 6.0结合使用),这很容易从函数调用跳转到函数定义。它只是在编码时要求更多的纪律。