web-dev-qa-db-ja.com

短絡を呼び出すような短絡Array.forEach

[1,2,3].forEach(function(el) {
    if(el === 1) break;
});

JavaScriptの新しいforEachメソッドを使ってこれを行うにはどうすればよいですか?私はreturn;return false;およびbreakを試しました。 breakはクラッシュし、returnは何もしないで繰り返しを続けます。

1248
Scott

breakforEachへの組み込み機能はありません。実行を中断するには、なんらかの例外をスローする必要があります。例えば。

var BreakException = {};

try {
  [1, 2, 3].forEach(function(el) {
    console.log(el);
    if (el === 2) throw BreakException;
  });
} catch (e) {
  if (e !== BreakException) throw e;
}

JavaScriptの例外はそれほど美しくありません。あなたが本当にその内部にforを必要とするならば、伝統的なbreakループがより適切かもしれません。

Array#some を使用

代わりに Array#some を使用してください。

[1, 2, 3].some(function(el) {
  console.log(el);
  return el === 2;
});

これは、配列順に実行されたいずれかのコールバックがsomeを返し、残りの実行を短絡するとすぐにtruetrueを返すためです。

some、その逆の everyreturn falseで停止します)、およびforEachはすべてECMAScript Fifth Editionのメソッドで、欠落しているブラウザのArray.prototypeに追加する必要があります。

1720
bobince

新しい for of loop を使用してECMAScript2015(別名ES6)でこれを実行するさらに良い方法があります。たとえば、次のコードでは5の後に配列要素は表示されません。

let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let el of arr) {
  console.log(el);
  if (el === 5) {
    break;
  }
}

ドキュメントから:

for ... in for ... of の両方のステートメントが、何かを繰り返します。それらの間の主な違いはそれらが反復するものにあります。 for ... in ステートメントは、オブジェクトの列挙可能なプロパティを元の挿入順で繰り返します。 for ... of ステートメントは、反復可能オブジェクトが反復対象として定義しているデータを反復します。

繰り返しにインデックスが必要ですか。 Array.entries() を使用できます。

for (const [index, el] of arr.entries()) {
  if ( index === 5 ) break;
}
275
canac

every methodを使用できます。

[1,2,3].every(function(el) {
    return !(el === 1);
});

ES6

[1,2,3].every( el => el !== 1 )

古いブラウザのサポート用

if (!Array.prototype.every)
{
  Array.prototype.every = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          !fun.call(thisp, this[i], i, this))
        return false;
    }

    return true;
  };
}

より詳細 ここ

176

MDNのArray.prototype.forEach() のドキュメントからの引用)

停止または中断する方法はありませんforEach()ループには、例外をスローする以外の方法があります。このような振る舞いが必要な場合は、.forEach()メソッドは間違ったツールです。代わりに単純なループを使用してください。述語について配列要素をテストしていてブール値の戻り値が必要な場合は、代わりに every() または some() )を使用できます。

@bobinceが示唆しているように、(疑問のある)あなたのコードには、代わりに Array.prototype.some() )を使ってください。

Array.prototype.some()は、コールバックが真の値(Booleanに変換されると真になる値)を返すものが見つかるまで、配列内に存在する各要素に対して1回コールバック関数を実行します。そのような要素が見つかると、some()は直ちにtrueを返します。それ以外の場合、some()はfalseを返します。コールバックは、値が割り当てられている配列のインデックスに対してのみ呼び出されます。削除されたインデックスや値が割り当てられたことのないインデックスに対しては呼び出されません。

60
Rahul Desai

残念ながら、この場合はforEachを使わない方がずっと良いでしょう。代わりに通常のforループを使用してください。そうすれば、期待通りに動作するようになります。

var array = [1, 2, 3];
for (var i = 0; i < array.length; i++) {
  if (array[i] === 1){
    break;
  }
}
56
Weston Ganger

コールバック関数内でfalseを返すことができるので、jqueryeachメソッドを使用することを検討してください。

$.each(function(e, i) { 
   if (i % 2) return false;
   console.log(e)
})

Lodashライブラリはmap/reduce/foldなどと連鎖できる takeWhile メソッドも提供します。

var users = [
  { 'user': 'barney',  'active': false },
  { 'user': 'fred',    'active': false },
  { 'user': 'pebbles', 'active': true }
];

_.takeWhile(users, function(o) { return !o.active; });
// => objects for ['barney', 'fred']

// The `_.matches` iteratee shorthand.
_.takeWhile(users, { 'user': 'barney', 'active': false });
// => objects for ['barney']

// The `_.matchesProperty` iteratee shorthand.
_.takeWhile(users, ['active', false]);
// => objects for ['barney', 'fred']

// The `_.property` iteratee shorthand.
_.takeWhile(users, 'active');
// => []
23
vittore

あなたのコード例から、Array.prototype.findがあなたが探しているものであるように見えます: Array.prototype.find() そして Array.prototype.findIndex()

[1, 2, 3].find(function(el) {
    return el === 2;
}); // returns 2
17
Oliver Moran

Dean Edwardの提案 を使用してエラーをキャッチせずにループから抜け出すためにStopIterationエラーをスローする場合は、次の関数を使用できます( 元々ここから )。

// Use a closure to prevent the global namespace from be polluted.
(function() {
  // Define StopIteration as part of the global scope if it
  // isn't already defined.
  if(typeof StopIteration == "undefined") {
    StopIteration = new Error("StopIteration");
  }

  // The original version of Array.prototype.forEach.
  var oldForEach = Array.prototype.forEach;

  // If forEach actually exists, define forEach so you can
  // break out of it by throwing StopIteration.  Allow
  // other errors will be thrown as normal.
  if(oldForEach) {
    Array.prototype.forEach = function() {
      try {
        oldForEach.apply(this, [].slice.call(arguments, 0));
      }
      catch(e) {
        if(e !== StopIteration) {
          throw e;
        }
      }
    };
  }
})();

上記のコードにより、独自のtry-catch句を使用しなくても、次のようなコードを実行することができます。

// Show the contents until you get to "2".
[0,1,2,3,4].forEach(function(val) {
  if(val == 2)
    throw StopIteration;
  alert(val);
});

覚えておくべき重要なことの1つは、Array.prototype.forEach関数が既に存在する場合にのみ更新されることです。まだ存在しない場合は、変更されません。

14
Chris West

簡単な答え:これにはfor...breakを使用するか、forEachを壊さないようにコードを変更してください。 for...breakをエミュレートするために.some()または.every()を使用しないでください。 for...breakループを回避するようにコードを書き直すか、for...breakを使用してください。 for...breakの代わりにこれらの方法を使うたびに、神は子猫を殺します。

長い答え:

.some().every()は両方ともboolean値を返し、.some()は渡された関数がtrueを返す場合はtrueを返し、渡された関数がfalseを返す場合はeveryはfalseを返します。これがその機能の意味です。意味のないことに関数を使用することは、CSSの代わりにレイアウトのためにテーブルを使用するよりもはるかに悪いです。なぜなら、それはあなたのコードを読む誰もがいらいらするからです。

また、これらのメソッドをfor...breakの代替として使用する唯一の方法は、副作用を発生させること(.some() callback関数の外側でいくつかの変数を変更すること)であり、これはfor...breakと大差ありません。

したがって、.some()または.every()for...breakループの代替として使用しても、副作用がないわけではありません。これは、for...breakよりもそれほど明確ではないため、これは苛立たしい方法です。

for...breakに必要がないように、いつでもコードを書き直すことができます。 .filter()を使用して配列をフィルター処理したり、.slice()を使用して配列を分割したりすることができます。その後、配列のその部分に.forEach()または.map()を使用します。

8
Max

別のサイトでこの解決策を見つけました。 forEachをtry/catchシナリオでラップすることができます。

if(typeof StopIteration == "undefined") {
 StopIteration = new Error("StopIteration");
}

try {
  [1,2,3].forEach(function(el){
    alert(el);
    if(el === 1) throw StopIteration;
  });
} catch(error) { if(error != StopIteration) throw error; }

詳細はこちら: http://dean.edwards.name/weblog/2006/07/enum/

4
RussellUresti

これは私が問題を解決するために思いついたものです。

Array.prototype.each = function(callback){
    if(!callback) return false;
    for(var i=0; i<this.length; i++){
        if(callback(this[i], i) == false) break;
    }
};

そして、それを使ってそれを呼ぶでしょう:

var myarray = [1,2,3];
myarray.each(function(item, index){
    // do something with the item
    // if(item != somecondition) return false; 
});

コールバック関数内でfalseを返すと、ブレークが発生します。それが実際にうまくいかないかどうか私に知らせてください。

3
tennisgent

反復後に配列にアクセスする必要がない場合は、配列の長さを0に設定することで回避できます。反復後も配列の長さを必要とする場合は、スライスを使用してクローンを作成できます。

[1,3,4,5,6,7,8,244,3,5,2].forEach(function (item, index, arr) {
  if (index === 3) arr.length = 0;
});

またはクローンを使って:

var x = [1,3,4,5,6,7,8,244,3,5,2];

x.slice().forEach(function (item, index, arr) {
  if (index === 3) arr.length = 0;
});

これは、コードにランダムなエラーを投げるよりはるかに優れた解決策です。

3
3rdEden

これはforループですが、forEach()のようにループ内でオブジェクト参照を維持しますが、実行することもできます。

var arr = [1,2,3];
for (var i = 0, el; el = arr[i]; i++) {
    if(el === 1) break;
}
3
jamos

私が思い付いたもう一つの概念:

function forEach(array, cb) {
  var shouldBreak;
  function _break() { shouldBreak = true; }
  for (var i = 0, bound = array.length; i < bound; ++i) {
    if (shouldBreak) { break; }
    cb(array[i], i, array, _break);
  }
}

// Usage

forEach(['a','b','c','d','e','f'], function (char, i, array, _break) {
  console.log(i, char);
  if (i === 2) { _break(); }
});
2
c24w

forEach構文を使い続けたい場合は、これを効率的にする方法です(ただし、通常のforループほどは良くありません)。ループから抜け出したいかどうかを知っている変数があるかどうかすぐに調べてください。

この例では、 done 情報を格納する必要があるforEachの周囲に function scope を作成するために無名関数を使用しています。

(function(){
    var element = document.getElementById('printed-result');
    var done = false;
    [1,2,3,4].forEach(function(item){
        if(done){ return; }
        var text = document.createTextNode(item);
        element.appendChild(text);
        if (item === 2){
          done = true;
          return;
        }
    });
})();
<div id="printed-result"></div>

私の二セント。

1
Justus Romijn

さらに別のアプローチ

        var wageType = types.filter(function(element){
            if(e.params.data.text == element.name){ 
                return element;
            }
        });
        console.dir(wageType);
1
Harry Bosh

私はそれが正しい方法ではないことを知っています。それはループを壊すことではありません。それはJugadです

let result = true;
[1, 2, 3].forEach(function(el) {
    if(result){
      console.log(el);
      if (el === 2){
        result = false;
      }
    }
});
1
Durgpal Singh

そのために nullhack を使用します。これはnullのプロパティにアクセスしようとしますが、これはエラーです。

try {
  [1,2,3,4,5]
  .forEach(
    function ( val, idx, arr ) {
      if ( val == 3 ) null.NULLBREAK;
    }
  );
} catch (e) {
  // e <=> TypeError: null has no properties
}
//
1
public override

前述のように、.forEach()を壊すことはできません。

ES6イテレータを使ったforeachのもう少し現代的な方法は次のとおりです。反復時にindex/valueに直接アクセスできるようにします。

const array = ['one', 'two', 'three'];

for (const [index, val] of array.entries()) {
  console.log('item:', { index, val });
  if (index === 1) {
    console.log('break!');
    break;
  }
}

出力:

item: { index: 0, val: 'one' }
item: { index: 1, val: 'two' }
break!

リンク集

1
Alex

あなたは私のために働く以下のコードに従うことができます。

 var     loopStop = false;
YOUR_ARRAY.forEach(function loop(){
    if(loopStop){ return; }
    if(condition){ loopStop = true; }
});

array.prototype.every関数を使用します。これは、ループを解消するためのユーティリティを提供します。こちらの例を参照してください Mozilla開発者ネットワーク上のJavascriptドキュメント

0
Yiling

私はfor inを使うのが好きです

var words = ['a', 'b', 'c'];
var text = '';
for (x in words) {
    if (words[x] == 'b') continue;
    text += words[x];
}
console.log(text);

for inforEachとよく似た働きをします。内部にreturn to exit関数を追加することができます。パフォーマンスも向上しました。

0
Jorge Alberto

あなたはまだすべての要素を循環させるので、これは最も効率的ではありません、しかし私はそれが非常に単純なものを考える価値があるかもしれないと思いました:

let keepGoing = true;
things.forEach( (thing) => {
  if (noMore) keepGoing = false;
  if (keepGoing) {
     // do things with thing
  }
});
0
martyman

"find"で試してください。

var myCategories = [
 {category: "start", name: "Start", color: "#AC193D"},
 {category: "action", name: "Action", color: "#8C0095"},
 {category: "exit", name: "Exit", color: "#008A00"}
];

function findCategory(category) {
  return myCategories.find(function(element) {
    return element.category === category;
  });
}

console.log(findCategory("start"));
// output: { category: "start", name: "Start", color: "#AC193D" }

支持を得て、@ bobinceに同意します。

また、FYI:

Prototype.jsには、この目的のためのものがあります。

<script type="text/javascript">
  $$('a').each(function(el, idx) {
    if ( /* break condition */ ) throw $break;
    // do something
  });
</script>

$breakは、Prototype.jsによって内部的にキャッチされ処理され、 "each"サイクルを中断しますが、外部エラーを生成しません。

詳細については Prototype.JS API を参照してください。

jQueryにも方法があります。ループを早期に中断するには、ハンドラにfalseを返すだけです。

<script type="text/javascript">
  jQuery('a').each( function(idx) {
    if ( /* break condition */ ) return false;
    // do something

  });
</script>

詳細については jQuery API を参照してください。

0

あなたのケースのようにすでにあなたの配列にある要素の値に基づいてブレークする必要があるならば(すなわち、ブレーク条件が配列にその要素値が割り当てられた後に変わるランタイム変数に依存しないなら)以下のように slice()indexOf() の関係.

ForEachが 'Apple'に到達したときに中断する必要がある場合は、次のものを使用できます。

var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var fruitsToLoop = fruits.slice(0, fruits.indexOf("Apple"));
// fruitsToLoop = Banana,Orange,Lemon

fruitsToLoop.forEach(function(el) {
    // no need to break
});

述べたように W3Schools.comで / slice()メソッドは、新しい配列オブジェクトとして、配列内の選択された要素を返します。元の配列は変更されません。

JSFiddle でそれを参照してください。

誰かに役立つことを願っています。

0
Ula