打破数组循环函数(map、forEach 等)的循环

Break the loop of an Array looping function (map, forEach, etc.)

如何从数组的隐式循环中中断(类似于 break 语句)?

Array.prototype.mapArray.prototype.forEach 等函数暗示对数组元素的循环。我想有条件地提前打破这个循环。

这个人为的例子:

const colours = ["red", "orange", "yellow", "green", "blue", "violet"];

colours.map(item => {
    if (item.startsWith("y")) {
        console.log("The yessiest colour!");
        break;
    }
});

导致 SyntaxError: Illegal break statement.

我怎样才能像 break 语句那样打破循环?

尽管 forEach 被设计为 运行 一些 不会 改变数组的功能(即它被设计为做一些其他的副作用每个项目),它被明确记录为无法打破循环。

来自MDN documentation for forEach:

There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool.

因此,尽管 forEach 是为副作用而设计的,但无法正常访问循环的控制结构。

因为 Array.prototype.mapArray.prototype.reduce 旨在生成一个新值,所以它们不是为早期中断等副作用而设计的。文档似乎没有明确说明这一点。


尝试的可能替代方法:重新编写代码以使用 Array.prototype.some or Array.prototype.every。当它们的条件已知时(当 some 将 return true,或当 every 将 return false).

colours.prototype.some(item => {
    if (item.startswith("y")) {
        console.log("The yessiest colour!");
        return true;
    }
});

你不能用常规的方法来做。您可以通过记住循环是否为 "broken" 来模拟 break 行为。缺少此解决方案的是循环实际上仍在继续(尽管跳过了迭代逻辑)。

let isBroken = false;

colours.map(item => {
    if (isBroken) {
        return;
    }
    if (item.startsWith("y")) {
        console.log("The yessiest colour!");
        isBroken = true;
        return;
    }
});

您的示例的最佳解决方案是使用普通 for 循环。

for (colour of colours) {
    if (colour.startsWith("y")) {
        console.log("The yessiest colour!");
        break;
    }
}

您也可以使用一种肮脏的方式来真正停止 map 循环。

colours.map((item, index, array) => {
    if (item.startsWith("y")) {
        console.log("The yessiest colour!");
        array.splice(0, index);
    }
});
// The colours array will be modified after this loop

如果您唯一的选择是使用 Array.forEach

,您可以抛出异常

参考这个:

How to short circuit Array.forEach like calling break?

还有其他可用的方法也可以解决您的问题。例如,你可以使用方法:Array.prototype.some() 如果你想检查某些条件并根据该条件打破循环。

例子可以参考这里:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

Array#mapArray#forEach 等从未被设计为停止。这会让人感到奇怪,因为 mapforEach 的意图实际上是遍历所有项目。

此外,我认为不可能通知调用者发生了 break 事件,因为它在函数中,而不是原始循环的组成部分。

所以让我们看看在第一次出现 true 时停止循环而没有 return 匹配值本身的自定义方法:

Object.defineProperty(Array.prototype, 'untilTrue', {
    enumerable: false,
    value: function(lambda) { 
     for(let i in this) {
       if(lambda.call(this, this[i])) return;
      }
    }
});

const colours = ["red", "orange", "yellow", "green", "blue", "violet"];

colours.untilTrue(item => {
    if (item.startsWith("y")) {
        console.log("The yessiest colour!");
        return true;
    }
    console.log(item);
});

比较此习俗 untilTrueArray#find 的使用:

const colours = ["red", "orange", "yellow", "green", "blue", "violet"];

colours.find(item => {
    if (item.startsWith("y")) {
        console.log("The yessiest colour!");
        return true;
    }
    return false;
});

唯一值得注意的区别是 untilTrue 没有 return 匹配项 - Array#find 除了调用 lambda.

所以一般来说,我会坚持使用 Array#find 以保持代码整洁干净,并像这样使用它:

const colours = ["red", "orange", "yellow", "green", "blue", "violet"];

if(colours.find(item => item.startsWith("y")) !== undefined) {
  console.log("The yessiest colour!");
}

这会在第一个匹配项(和 returns 匹配元素)处停止循环。另请注意,您必须与 undefined 进行比较 - 如果您正在搜索 falsenull 值,则如果仅与 true.

您可以通过这些方式创建自定义的 forEach 方法

Array.prototype._forEach = function (callback) {
  let _break = false;

  const breaker = () => {
    _break = true;
  };

  for (let index = 0; index < this.length; index++) {
    if (_break) break;

    callback(this[index], index, breaker);
  }
};


// Example for usage:

const letters = ["a", "b", "c", "d", "e", "f", "g"];

letters._forEach((data, index, breaker) => {
  if (data === "c") return; // continue role

  if (data === "e") return breaker(); // break role

  console.log(`log ${index}:  ${data}`);
});

/**
 * result:
 *  log 0:  a
 *  log 1:  b
 *  log 3:  d
 */

或者您可以通过创建

来创建顶级自定义 forEach 方法
function forEach(items, callback) {
  let _break = false;

  const breaker = () => {
    _break = true;
  };

  for (let index = 0; index < items.length; index++) {
    if (_break) break;

    callback(items[index], index, breaker);
  }
}