もっと速くするために(文字列変換コストの抑止 + String.prototype 化による影響)
element.style.xxx にアクセスするには、"css-prop" ではなく "cssProp" の形でアクセスする必要があります。このとき行われる変換は camelize と呼ばれ、JavaScriptライブラリの多くは以下のような実装を行っているようです。
# camelize = らくだのこぶのようにする
function camelize(name) { // "text-align" -> "textAlign" return name.replace(/\-(\w)/g, function(m, c){ return c.toUpperCase(); }); }
その都度 camelize するのではなく事前生成しておけば速くなるのでしょうか?
比較対照が無いと寂しいので、代表的な JavaScript ライブラリのスコアも一緒に計ってみました。
テスト方法と、事前予想
- job1 = Hash を事前生成しておいて Hash を検索
- Hash 検索のみなので軽いハズ
- Hash が Google Chrome(V8) の隠しクラスとして最適化されればすごく速くなりそう。
- job2 = オンデマンドで生成
- 最長ルートだと 組み込み関数x3 + 匿名関数の生成x1 が発生するからやや遅いハズ
- job3 = Prototype-1.6.1 の String.prototype.camelize()
- Prototype のコードは具沢山なので遅そう
- job4 = jQuery-1.3.2 のコードの断片
- 標準的なコード。Prototype-1.6.1 よりはきっと速いハズ
// ソースコードは概ねこんな感じ var _TIDY_ALIAS1, _TIDY_ALIAS2 = _ie67 ? { "float": "styleFloat", cssFloat: "styleFloat" } : { "float": "cssFloat", styleFloat: "cssFloat" }, _CAMELIZE = /-([a-z])/g, mix(_TIDY_ALIAS1, _TIDY_ALIAS2, _uncamelize(document.getElementsByTagName("html")[0].style)); function tidy1() { return _TIDY_ALIAS1[this] || this; } function _uncamelize(props) { var rv = {}, i, v, IGNORE = /^[A-Z-]/, UNCAMELIZE = /([a-z])([A-Z])/g; for (i in props) { if (!IGNORE.test(i) && typeof props[i] === "string") { v = i.replace(UNCAMELIZE, function(m, c, C) { return c + "-" + C.toLowerCase(); }); if (v !== i) { rv[v] = i; // { text-align: "textAlign" } } } } return rv; } function tidy2() { return _TIDY_ALIAS2[this] || ((this.indexOf("-") < 1) ? this : this.replace(_CAMELIZE, function(m, c) { return c.toUpperCase(); })); } String.prototype.tidy1 = tidy1; String.prototype.tidy2 = tidy2; var A = "background-color,text-align,-webkit-box-shadow,align,for,float".split(","); function job1(A) { var v,i=0; while((v=A[i++])){v.tidy1();} } function job2(A) { var v,i=0; while((v=A[i++])){v.tidy2();} } function job3(A) { var v,i=0; while((v=A[i++])){v.camelize();} // prototype.js } function job4(A) { var v,i=0; while((v=A[i++])){jQueryCamelCase(v);} // jQuery } } function jQueryCamelCase(name) { return name.replace(/\-(\w)/g, function(all, letter){ return letter.toUpperCase(); }); }
結果
job1 | job2 | 速度差(job2 / job1) | job3(prototype.js) | job4(jQuery) | ||
IE6 | 1360 | 2610 | 2倍 | 3922 | 2953 | |
IE8 | 656 | 1000 | 1.5倍 | 1703 | 1188 | |
Firefox2 | 625 | 1250 | 2倍 | 2109 | 1093 | |
Firefox3 | 124 | 299 | 2.4倍 | 414 | 339 | |
Firefox3.5.3(JIT) | 138 | 253 | 1.8倍 | 366 | 232 | |
Opera9.64 | 140 | 516 | 3.7倍 | 484 | 1047 | |
Opera10.0 | 125 | 609 | 4.9倍 | 547 | 1109 | |
Safari4 | 26 | 129 | 5倍 | 211 | 180 | |
Chrome4 | 23 | 125 | 5.4倍 | 301 | 169 |
ほぼ予想通りのスコアが出ました。が… Opera で job3 >> job4 なのは、prototype.js が正規表現を使わずに camelize を実装している(↓)からなのでしょうね。Opera の正規表現エンジンはかなり遅いので。
function camelize() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) : parts[0]; for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); return camelized; }
実装方法による差があるのか(String.prototype vs 関数)
String.prototype.tidy1() と 関数の形で実装した functionTidy1() も試してみました。
window.functionTidy1 = function(name) { return _TIDY_ALIAS1[name] || name; } function job1a(A) { var v,i=0; while((v=A[i++])){ functionTidy1(v); } } function job1b(A) { var v,i=0, fn = functionTidy1; // alias while((v=A[i++])){ fn(v); } }
job1 | job1a | job1b | |
IE6 | 1360 | 1953 | 719 |
IE8 | 656 | 625 | 125 |
Firefox2 | 625 | 156 | 391 |
Firefox3 | 124 | 66 | 56 |
Firefox3.5.3(JIT) | 138 | 61 | 43 |
Opera9.64 | 140 | 79 | 78 |
Opera10.0 | 125 | 62 | 78 |
Safari4 | 26 | 28 | 18 |
Chrome4 | 23 | 24 | 24 |
まとめ
camelize はアニメーション等で多用されますが、ブラウザの再描画処理のほうが大幅に遅いので、camelize だけ速くしても大勢に影響は無いような気もします。
描画を速くする方法も色々とあるので、実際のアニメーション処理で体感に変化があるか試してみたいですね。
あれ? UNCAMELIZE? DECAMELIZE? どっちだろ。
あと Firefox3.5 はインストールしなおしたほうがいいんじゃないのか? これFirefox3.5 ⇒ Firefox 3.5.3 に上書インストールしたスコアを載せました。