嵌套循环中匿名闭包的可读性

我的一位朋友被所有过于着名的'循环'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

    上一篇: Readibility of anonymous closures in nested loops

    下一篇: Scale UIView without scaling subviews