もっと速くするために(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 等は使わないほうが良いでしょう。