JavaScriptのforEach的なものでbreakしたい話


JavaScriptで毎回forループ書くのは面倒だし、そのためだけにライブラリ使うのもなぁという状態の時についこんなこんな感じの実装をしたりしてしまいます。


でこれだとbreakが出来ないので他のライブラリではどうやっているのかなぁと思ってみたので調べてみました。

ECMAScript5のforEach

  • 下記のような実装になっているためbreakは出来ないです

https://developer.mozilla.org/ja/JavaScript/Reference/Global_Objects/Array/forEach

  • someでそのようなことは実装出来るのでそちらを使えということなのかな

https://developer.mozilla.org/ja/JavaScript/Reference/Global_Objects/Array/some

jQueryのeach

  • ソースを見てみたところ、falseを返すとbreak出来るようです
  • ===による比較なので明示的にfalseを返さないとbreakしないので、意図せずに==による比較だとfalseになる値を返しても問題ないという感じでしょうか

https://github.com/jquery/jquery/blob/master/src/core.js

prototype.jsのeach

  • こちらもbreakは出来ないです。ECMAScript5のforEachが使える場合はそちらを使う実装になっているので当たり前ですが。

https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/array.js

  • ただこちらもsome関数があるのでそれを使えばbreakは出来そうです。

underscore.jsのeach

  • こちらもprototype.jsと同様にbreakは出来ないです。同じようにECMAScript5のforEachが使える場合はそちらを使う実装になっています

https://github.com/documentcloud/underscore/blob/master/underscore.js

  • ただこちらもsome関数があるのでそれを使えばbreakは出来そうです。


underscore.jsでECMAScript5のsomeが使えない時の実装が少し面白いなぁと思ったので紹介してみます。
内部的にはeachを使っていて、どうbreakを実装しているのかなぁと見てみると、

var breaker = {}

というローカル変数を定義しておいて、callbackがこのbreakerを返した場合だけbreakするようになっています。このbreakの機構はbreakerとの比較が===で行われているためunderscore.jsのライブラリでしか使うことが出来ないようになっています。

var breaker = {};
// each内の実装
for (var i = 0, l = obj.length; i < l; i++) {
  if (iterator.call(context, obj[i], i, obj) === breaker) return;
}

// someの実装
each(obj, function(value, index, list) {
  if (result || (result = iterator.call(context, value, index, list))) return breaker;
});

というわけで

jQueryのfalseを明示的に返したらbreakするというのも実装は楽でいいけどちょっとわかりにくいかなぁと思うので、やっぱりsomeなどの別の関数を用意して、breakさせたいときはeach(forEach)じゃなくてそちらを使うのがいいのかなぁとか思いました。