もっと速くするために(Array.forEach は控えめに)

追記

「forEach より for のほうが速いよ」って書くときに、一言「でも forEach を使わないとハマるコードもあるよ」と付け加えておいたほうがいいんでないの
forとforEach - JavaScriptで遊ぶよ - g:javascript

ごめんなさい、いつも端折ってます。

…と。その前に、このブログの存在意義を独り言しておきます。

このブログは、ソースコードにコメントとして埋めることができない情報(統計,画像,根拠,思考プロセス)をエントリとして公開し、エントリのURLをコメント(ポインタ)として埋め込むためのものです。つまり、Working Log であり、詳細な Change Log です。

// --- uuQuery.js での使用例
function visitedFilter(fid, negate, elms) {
  // :link(0x0e)  :visited(0x0f)
  var rv = [], ri = -1, v, i = 0, ok, cs, idx;

  // http://d.hatena.ne.jp/uupaa/20080928
  _mm.style.sheet.create(_styleSheetID);
  idx = _mm.style.sheet.insertRule(_styleSheetID, "a:visited",
                                   ie ? "ruby-align:center"
                                      : "outline:0 solid #000");
  while ( (v = elms[i++]) ) {
    ...

溜り場用の機能(コメント, ブクマ)を廃止していないのは、誤りや配慮不足について、お叱りを受け止め反省するためです。
# ついでに言うと、このログが停止/消滅したときは「uupaa は、JavaScript よりももっと楽しい事を見つけて逝ってしまったんだ」ということも分かります。


さて… 本題
Array.forEach は関数スコープをその都度生成するので、副作用のあるループを解決する良い方法の一つです。この文章がよく分からない方には、安全のため、常に Array.forEach を利用することをお勧めします(forEach が使えない環境もあります)。
スコープの存在と使いどころを理解している JavaScript 中上級者にとっては、Array.forEach は状況に応じて利用すべき機能です。その根拠の一端が以下になります。

本文

去年測定した配列のループコストのデータが出てきました。最新のブラウザのスコアを追加したものを張ってみます。

  • while( (v = ary[i++]) )
    • 条件として、要素の欠落が無く true として評価される要素だけを含んだ配列。または空の配列
  • for(;;)
  • Array.forEach
  • jQuery.each
<!doctype html><html><head><title></title></head><body>
<script src="jquery-1.3.2.js"></script>
<script>
window.onload = function() {
  var plan = [job1, job2, job3, job4];

  function run() {
    var v = plan.shift();
    if (v) {
      test(v);
      setTimeout(run, 0);
    }
  }
  setTimeout(run, 0);
};

function test(fn) {
  var a = "12345678901234567890123456789012345678901234567890".split("");
  var now = +new Date, loop = 10000;

  while (loop--) {
    fn(a);
  }
  document.getElementById("view").innerHTML += ((+new Date) - now) + "<br />";
}

function job1(A) {
  var v,i=0;while((v=A[i++])){v;}
}
function job2(A) {
  var v,i=0,iz=A.length;for(;i<iz;++i){v=A[i];}
}
function job3(A) {
  A.forEach(function(v,i){v;});
}
function job4(A) {
  jQuery.each(A,function(i,v){v;});
}

// Array.prototype.forEach
function ArrayForEach(callback,  // @param Function: evaluator
                      thisArg) { // @param ThisObject(= undefined):
  for (var i = 0, iz = this.length; i < iz; ++i) {
    (i in this) && callback.call(thisArg, this[i], i, this);
  }
}
Array.prototype.forEach || (Array.prototype.forEach = ArrayForEach);

</script>
<div id="view"></div>
</body></html>
job1 job2 job3 job4 速度差(最遅/最速)
IE6 625 625 7266 8266 13倍
IE7 234 249 3151 3712 16倍
IE8 188 172 1638 1031 6倍
Firefox2 156 235 1625 4422 28倍
Firefox3 85 139 84 678 8倍
Firefox3.5(JIT) 7 5 233 1405 201倍
Opera9.64 188 218 579 765 4倍
Opera10.0 172 187 578 828 5倍
Chrome4 13 5 29 109 22倍
Safari4 16 7 104 60 9倍
TOTAL 1684 1842 15287 21276 平均13倍

空ループ

配列の要素にアクセスしない空ループを測定すると以下のような結果に。

function jobx(A) {
//var v,i=0,iz=A.length;for(;i<iz;++i){v=A[i];}
  var v,i=0,iz=A.length;for(;i<iz;++i){}
}
jobx
IE6 250
IE7 93
IE8 63
Firefox2 140
Firefox3 82
Firefox3.5 3
Opera9.64 125
Opera10.0 93
Chrome4 3
Safari4 4
TOTAL 856

他のループとは前提が異なるため、スコアの比較には使えません。

えと

forEach は 10得ナイフです。軽さが求められる場所では、Array.forEach, every, some, map, filter 等は使わないほうが良いでしょう。