JavaScript中跳出嵌套循环的最佳方法是什么?

636

如何在Javascript中最好地从嵌套循环中跳出?

//Write the links to the page.
for (var x = 0; x < Args.length; x++)
{
   for (var Heading in Navigation.Headings)
   {
      for (var Item in Navigation.Headings[Heading])
      {
         if (Args[x] == Navigation.Headings[Heading][Item].Name)
         {
            document.write("<a href=\"" 
               + Navigation.Headings[Heading][Item].URL + "\">" 
               + Navigation.Headings[Heading][Item].Name + "</a> : ");
            break; // <---HERE, I need to break out of two loops.
         }
      }
   }
}

这是一个很好的示例,展示了如何从循环和代码块中跳出:https://marcin-chwedczuk.github.io/break-out-of-code-block-in-java-and-javascript - csharpfolk
18个回答

1448

就像Perl一样,

loop1:
    for (var i in set1) {
loop2:
        for (var j in set2) {
loop3:
            for (var k in set3) {
                break loop2;  // breaks out of loop3 and loop2
            }
        }
    }

根据EMCA-262第12.12节的定义。[MDN Docs]

与C语言不同,这些标签只能用于continuebreak,因为Javascript没有goto


538
为什么我在使用 JavaScript 的三年中从未见过有人使用这个东西 :/.. - salmatron
58
MDN表示“纯粹出于可读性考虑,避免使用标签”。为什么它不“可读”?因为当然没有人使用它们。但是为什么他们不使用它们呢?... - XML
8
@Web_Designer,我认为你的评论已经过时了。MDN文档中并没有说“避免使用标签”。请考虑修改或删除你的评论。 - Sean the Bean
10
@SeantheBean 已完成。这似乎是更直接的答案,不容易被滥用,因为只能使用 continuebreak - Gary Willoughby
49
@JérémyPouyet - 你的投票逻辑荒谬且无端。它完美地回答了OP的问题。这个问题并不涉及你对易读性的看法。请重新考虑你协助社区的方式。 - The Dembinski
显示剩余24条评论

235

将其封装在一个函数中,然后只需 return


15
我选择接受这个答案,因为它简单且可以以优雅的方式实现。我非常讨厌使用GOTO,并认为它们是一种糟糕的做法(引发争议),Ephemient的答案距离这种做法太近了。 ;o) - Gary Willoughby
18
只要不破坏结构,使用GOTO语句是可以的。但这取决于个人偏好! - ephemient
53
for循环中的标签与GOTO除了语法外绝对没有任何相似之处。它们只是一种从外部循环中跳出的方法。你没有问题打破最内层的循环,对吧?那么为什么你会有问题打破外部循环呢? - John Smith
14
请考虑接受另一个答案。如果没有 Andrew Hedges 的评论(顺便说一下,谢谢),我会认为:噢,JavaScript没有那个功能。我打赌许多人在社区也可能忽略这条评论并且依然持有相同观点。 - John Smith
13
为什么 Stack Overflow 没有一种功能来让社区覆盖明显错误的已选答案?:/ - Matt Huggins
显示剩余4条评论

103

虽然我有点晚了,但是以下是一种与语言无关的方法,它不使用GOTO/标签或函数封装:

for (var x = Set1.length; x > 0; x--)
{
   for (var y = Set2.length; y > 0; y--)
   {
      for (var z = Set3.length; z > 0; z--)
      {
          z = y = -1; // terminates second loop
          // z = y = x = -1; // terminate first loop
      }
   }
}

优点是代码结构自然,这应该会让不喜欢使用GOTO语句的人感到满意。缺点是内部循环需要完成当前迭代才能终止,因此在某些情况下可能不适用。


3
不应将左花括号放在新行上,因为 JavaScript 的实现可能会在前一行末尾插入冒号。 - Evgeny
27
虽然一些 JavaScript 样式指南要求大括号放在同一行,但将其放在新的一行并不是错误的,解释器也不会模棱两可地插入分号。自动分号插入 (ASI) 的行为已经被明确定义,并且在这种情况下不适用。 - Jason Suárez
11
请务必对这种方法进行评论。这里面的情况并不是立即显而易见的。 - Qix - MONICA WAS MISTREATED
3
我可能有所遗漏,但为了解决内部循环必须完成该迭代的问题,您是否可以在设置z和y之后立即放置一个breakcontinue?我确实喜欢使用for循环的条件来退出的想法。它以自己的方式非常优雅。 - Ben Sutton
3
赞赞赞,这是一种新颖的方法!但是这不适用于 for(var a in b){...} 或者 for(var a of b){...} 这种类型的循环。 - Jay Dadhania
显示剩余2条评论

86

我知道这是一个非常古老的话题,但由于我的标准方法还没有被提到过,所以我想将其发布给未来的谷歌搜索者。

var a, b, abort = false;
for (a = 0; a < 10 && !abort; a++) {
    for (b = 0; b < 10 && !abort; b++) {
        if (condition) {
            doSomeThing();
            abort = true;
        }
    }
}

2
如果在嵌套循环的第一次迭代中condition评估为true,则仍会运行其余10次迭代,并每次检查abort值。对于10次迭代来说,这不是性能问题,但如果是10000次迭代,则会成为性能问题。 - Robusto
9
不,它从两个循环中都退出。这是演示示例。无论您设置什么条件,一旦达成它,它就会退出。 - zord
7
优化方案是在设置 abort = true; 后添加一个 break; 并且从最后的循环中删除 !abort 条件检查。 - xer21
2
我喜欢这个,但是我认为从一般意义上讲,你会进行很多不必要的处理 - 也就是说,对于每个迭代器的每次迭代,都要评估“abort”和表达式。在简单的情况下,这可能没问题,但对于有数以亿计迭代的大型循环来说,这可能会成为一个问题。 - Bernardo Dal Corno
2
你们真的在争论检查单个布尔值10000次是快还是慢吗?试试每秒100百万次 /叹气 - fabspro
显示剩余6条评论

66

非常简单:

var a = [1, 2, 3];
var b = [4, 5, 6];
var breakCheck1 = false;

for (var i in a) {
    for (var j in b) {
        breakCheck1 = true;
        break;
    }
    if (breakCheck1) break;
}

1
我同意这是最好的方法,函数式编程不具有可扩展性,将所有for循环包装在if中也不具有可扩展性,即使阅读和调试也很困难... 这个方法真的很棒。你只需要声明变量loop1、loop2、loop3,并在结尾添加一个小语句。此外,要打破多个循环,您需要执行类似于“loop1 = loop2 = false;”的操作。 - Muhammad Umer
1
我使用了这种方式,它很有效,没有用不必要的函数使其复杂化。我只是在搜索是否像php一样js有break 2;之后才到达这里。 - Patanjali

53

以下是在JavaScript中打破嵌套循环的五种方法:

1)将父(母)循环设置为结束

for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
        {
            i = 5;
            break;
        }
    }
}

2) 使用标签

exit_loops:
for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
            break exit_loops;
    }
}

3) 使用变量

var exit_loops = false;
for (i = 0; i < 5; i++)
{
    for (j = 0; j < 5; j++)
    {
        if (j === 2)
        {
            exit_loops = true;
            break;
        }
    }
    if (exit_loops)
        break;
}

4) 使用自执行函数

(function()
{
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
             if (j === 2)
                 return;
        }
    }
})();

5) 使用常规函数

function nested_loops()
{
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
             if (j === 2)
                 return;
        }
    }
}
nested_loops();

4
@Wyck,我非常赞同!遗憾的是JavaScript没有像PHP中那样简单的语法break 2;。没有循环标签、函数、if-else检查、也不会改变/破坏循环变量——只有干净的语法! - Jay Dadhania
4
示例4很棒。 - leroyjenkinss24
@JayDadhania 抱歉,你的“简洁”和“易懂”的语法会给我们的软件引入错误。显式优于隐式。我想要自己命名我的标签。 - Ekrem Dinçel
@EkremDinçel,像break 2;这样的语法在JS中并不存在,那么它怎么会引入错误呢?你想手动标记循环吗?当然可以 - 我从未说过你不应该这样做。此外,我绝不建议使用3,2,1手动标记JS循环 - JS目前不允许仅使用数字手动标记循环。我只是希望这样的事情可以隐式地实现。此外,这样的语句已经成为一些非常流行的语言(如PHP)的核心部分,我还没有看到(m)有任何帖子“想要手动标记PHP循环,因为break 2;很难重构”。 - Jay Dadhania
@JayDadhania 肯定不会在JS中引入错误,因为它不存在。但是如果存在的话,它可能会引入错误。我想我在上面的评论中解释得有点不清楚。隐式转换确实存在问题,无论你是否建议,在JavaScript中人们都会使用它。你以PHP作为这种语法的例子,我认为你也应该注意到PHP的糟糕历史。我听说他们现在正在修复一些问题,但是很长一段时间以来,使用它编写的应用程序中有太多的意大利面条代码,这是有原因的。 - Ekrem Dinçel
显示剩余5条评论

44
var str = "";
for (var x = 0; x < 3; x++) {
    (function() {  // here's an anonymous function
        for (var y = 0; y < 3; y++) {
            for (var z = 0; z < 3; z++) {
                // you have access to 'x' because of closures
                str += "x=" + x + "  y=" + y + "  z=" + z + "<br />";
                if (x == z && z == 2) {
                    return;
                }
            }
        }
    })();  // here, you execute your anonymous function
}

这怎么样? :)


2
我想这就是swilliams所说的。 - harley.333
21
如果循环较大,这将增加显著的运行时成本 - 每次JavaScript解释器/编译器(或现在通常称为"compreter",两者混合)都必须创建一个新的函数执行上下文(并在某个时刻由GC释放)。 - Mörre
3
这实际上非常危险,因为可能会发生一些意料之外的怪异情况。特别是因为使用变量x创建了闭包,如果循环内部的逻辑在以后的某个时间引用了x(例如定义了一个内部匿名函数并将其保存并执行),那么x的值将是循环结束时的值,而不是该函数定义时的索引。 (续) - devios1
1
为了解决这个问题,您需要将 x 作为参数传递给匿名函数,以便它创建一个新副本,该副本可以被引用为闭包,因为它从那时起不会再改变。简而言之,我建议使用 ephemient 的答案。 - devios1
3
我认为易读性完全是一种无意义的说法。这比标签更模糊。标签只被看作是难以阅读,是因为没有人真正使用它们。 - Qix - MONICA WAS MISTREATED
显示剩余2条评论

17

不使用任何中断,终止标志或额外的条件检查怎么样?当满足条件时,此版本只会炸毁循环变量(使它们成为Number.MAX_VALUE),并迫使所有循环优雅地终止。

// No breaks needed
for (var i = 0; i < 10; i++) {
  for (var j = 0; j < 10; j++) {
    if (condition) {
      console.log("condition met");
      i = j = Number.MAX_VALUE; // Blast the loop variables
    }
  }
}

对于嵌套循环的递减类型,有一个类似的答案,但是这个方法适用于递增类型的嵌套循环,而且不需要考虑简单循环中每个循环的终止值。

另一个例子:

// No breaks needed
for (var i = 0; i < 89; i++) {
  for (var j = 0; j < 1002; j++) {
    for (var k = 0; k < 16; k++) {
      for (var l = 0; l < 2382; l++) {
        if (condition) {
          console.log("condition met");
          i = j = k = l = Number.MAX_VALUE; // Blast the loop variables
        }
      }
    }
  }
}

3
如果你使用Coffeescript,有一个方便的“do”关键字,使得定义和立即执行匿名函数更容易:
do ->
  for a in first_loop
    for b in second_loop
      if condition(...)
        return

因此,您可以简单地使用“return”来跳出循环。


这不一样。我的原始示例有三个 for 循环而不是两个。 - Gary Willoughby

2
如何将循环推至其极限?
    for(var a=0; a<data_a.length; a++){
       for(var b=0; b<data_b.length; b++){
           for(var c=0; c<data_c.length; c++){
              for(var d=0; d<data_d.length; d++){
                 a =  data_a.length;
                 b =  data_b.length;
                 c =  data_b.length;
                 d =  data_d.length;
            }
         }
       }
     }

1
我认为Drake的回答用更简洁明了的方式表达了相同的逻辑。 - Engineer Toast
绝对精彩! - geoyws

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接