uupaa looper 見っけたよー
(ε・◇・)з hasOwnProperty を使った for in ループより 20%~84% 低コストな、うーぱー式 ループのご紹介だよ~
Object.keys を使い、key を列挙することで、hasOwnProperty を使った for in ループよりも速くなります。
var keys = Object.keys(obj), i = 0, iz = keys.length; for (; i < iz; ++i) { var key = keys[i]; var value = obj[key]; ...
ベンチマーク
http://jsperf.com/perf-for-in-loop-vs-pre-enum-keys-for-loop/8
Browser | for_in_hasOwnProperty loop | uupaa-looper |
Chrome 16.0.912.77 | 14,765 (75% slower) | 60,038 |
Firefox 10 | 33,655 (43% slower) | 59,113 |
IE 9.0.4 (x86) | 18,888 (67% slower) | 57,784 |
IE 10 pp2 | 18,808 (72% slower) | 66,385 |
Opera 11.61 | 16,365 (21% slower) | 20,706 |
iPhone 4S (iOS 5.0.1) | 2,265 (57% slower) | 5,211 |
iPad 2 (iOS 5.0.1) | 2,674 (58% slower) | 6,296 |
Galaxy Nexus (Android 4.0.1) | 2,753 (80% slower) | 14,064 |
Galaxy Nexus Chrome β (Android 4.0.1) | 2,082 (84% slower) | 12,652 |
<script> // 一般的なやり方の for-in ループです // 毎回 hasOwnProperty() を呼びます function for_in_hasOwnProperty(hash) { var rv = [], key; for (key in hash) { if (hash.hasOwnProperty(key)) { rv.push(key); } } return rv; } // 一般的ではありませんが、限定的な用途で使える for-in ループです // 毎回 hasOwnProperty() を呼ばないため、多少速くなります function for_in(hash) { var rv = [], key; for (key in hash) { rv.push(key); } return rv; } // Object.keys でキーを列挙しておく新しいやり方です // for in + hasOwnProperty や for in ループよりも基本的に高速です function uupaa_looper(hash) { var rv = [], ary = Object.keys(hash), i = 0, iz = ary.length; for (; i < iz; ++i) { rv.push(ary[i]); } return rv; }
テストデータ
function createTestData(count) { var rv = {}, i = 0; for (; i < count; ++i) { rv[i] = i; } return rv; } function times(num, fn, arg) { var now = Date.now(), i = 0; for (; i < num; ++i) { fn(arg); } console.log(fn.name, Date.now() - now); } Object.prototype.oreore1 = "oreore1"; Object.prototype.oreore2 = "oreore2"; Object.prototype.oreore3 = "oreore3"; Object.prototype.oreore4 = "oreore4"; // 1000 times x 200 keys var testData = createTestData(200); times(1000, for_in_hasOwnProperty, testData); times(1000, for_in, testData); times(1000, uupaa_looper, testData); // 1 times x 10000 keys testData = createTestData(10000); times(1, for_in_hasOwnProperty, testData); times(1, for_in, testData); times(1, uupaa_looper, testData); </script>
つまり?
- for (key in object) な for-in ループでは object の prototype チェーンをたどってしまい、object に本来無関係なメソッドやプロパティ(oreore等)も列挙してしまいます
- 無関係な prototype メンバーのピックアップを避けるには hasOwnProperty を使い除外します
- JavaScript は関数呼び出しコストが高いので、ループ毎に hasOwnProperty を呼ぶとその分だけ遅くなります
- for-in ループには、ループを開始する前に key を列挙しリストを作成するといった見えない処理が挟まります
- リスト作成処理には、object の prototype チェーンを順番に辿り、同じ名前を除外するといった、重めの処理が隠れているのです
- for_in が uupaa_looper よりも遅いのは、この「見えない処理」が関係しています
- リスト作成処理には、object の prototype チェーンを順番に辿り、同じ名前を除外するといった、重めの処理が隠れているのです
- Object.keys(object) は prototype メンバーを列挙せずに、object に関連する物だけを列挙します。速いです
といった条件を加味し、事前に Object.keys で作成し、それを for ループで… というのがこのやり方です。
(ε・◇・)з このやり方は既出かもしれないけど、さっきやっと気づいたんだよね
(ε・﹏・)з 名前が無いと検索しづらいから「uupaa looper」って名前つけといたよ
レガシーブラウザ対策も
これで Object.keys が使えるようになります。
Object.keys = function(source) { var rv = [], key, ri = 0; for (key in source) { if (source.hasOwnProperty(key)) { rv[ri++] = key; } } return rv; };
(ε・◇・)っ rv[ri++] = key; は rv.push(key); よりも高速なのです in レガシーブラウザ
(ε・◇・)っ Chrome では push のほうが速いのです #豆
終わりに
クラスを派生させる構造を基礎としている 某JavaScript ゲームライブラリなどは、このようなやり方にすると、より速くなるのではないでしょうか。
また、ary = Object.keys(object).sort() とすることで、キーの出現順を安定させクロスブラウザにする事も可能になります(ちょっとコスト増えますが)。
(ε・◇・)з それ既に名前ついてるよー とかありましたら @uupaa までー
(ε・◇・)з js で有効なTips は ActionScript でも有効な Tips だったりしませんか? (だれか~