嵌套循环中匿名闭包的可读性
我的一位朋友被所有过于着名的'循环'javascript问题中的匿名函数所困扰。 (它已被解释为死亡,我实际上期待有人把我的问题视为重复,这可能是公平的游戏)。
这个问题相当于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或其他网站上提供了一些解决方案(当你知道这是一个常见问题时,谷歌是你的朋友)。
当然,真正的答案是在JS中,范围在功能级别。 所以当匿名函数运行时,'i'没有被定义在它们中的任何一个范围内,而是在全局范围内定义的,它具有循环结束的值4。
由于我们都很可能接受过使用块级作用域的语言的培训,所以它只是一点点不同于世界的其余部分(这意味着什么,任何人?)
令我感到厌烦的是,普遍的答案,甚至是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);
};
就内存使用情况,创建的范围数量,可能的泄漏而言,这些解决方案是否完全相同?
对不起,如果这真的是一个常见问题或主观或任何。
解决方案#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 create anonymous function”相反并执行它立即“从#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程序员。 或者对正则表达式感兴趣的人。
在我看来,最后一个模式(使用预定义的命名函数)是最易读的,“可调试的”,并且最重要的是我的编辑器(KomodoEdit或Visual Studio与Resharper 6.0结合使用)最易于使用,它很容易跳到函数调用的函数定义。 它只是在编码方面多一点纪律。
链接地址: http://www.djcxy.com/p/53619.html