qiitaでこんな記事がありました。
innerText(textContent)/innerHTMLを使わずJavaScriptでHTMLエスケープ - Qiita
で、思い出したのですが「文字クラスでreplaceを一度で済ますより、単純なマッチを直列で繰り返したほうが速い」って話しをどこかで聞いた覚えがあるので、どのぐらい差があるのか、ちょっと試してみました。
念のため、元記事の関数でテーブル参照をしないバージョン(escapeCharClassEx
)も用意。
3パターンのescape関数
// 元記事の関数 var escapeCharClass = function(content) { var TABLE_FOR_ESCAPE_HTML = { "&": "&", "\"": """, "<": "<", ">": ">" }; return content.replace(/[&"<>]/g, function(match) { return TABLE_FOR_ESCAPE_HTML[match]; }); }; // 念のためテーブル参照しないバージョン var escapeCharClassEx = function(content) { return content.replace(/[&"<>]/g, function(match) { switch(match){ case "&": return "&"; case "\"": return """; case "<": return "<"; case ">": return ">"; } }); }; // 4回replaceバージョン var escapeSerial = function(content){ return content .replace(/&/g, "&") .replace(/"/g, ""e;") .replace(/</g, "<") .replace(/>/g, ">"); };
検証プログラム
以下の様な感じで、入力文字が短い場合と長い場合の双方で速度を比較してみました。
var replicateString = function(text, times){ var ret = []; for(var i = 0; i < times; i++){ ret.push(text); } return ret.join("\n"); }; var testEscapeSpeed = function(){ var text = "hoge&hage>hige<hoge"; var text_short = replicateString(text, 10); var text_long = replicateString(text_short, 100); var replace_many = function(text, replace_fn, times){ times = times || 1000; for(var i = 0; i < times; i++){ replace_fn(text); } }; console.time("char class short"); replace_many(text_short, escapeCharClass); console.timeEnd("char class short"); console.time("char class short ex"); replace_many(text_short, escapeCharClassEx); console.timeEnd("char class short ex"); console.time("serial short"); replace_many(text_short, escapeSerial); console.timeEnd("serial short"); console.time("char class long"); replace_many(text_long, escapeCharClass); console.timeEnd("char class long"); console.time("char class long ex"); replace_many(text_long, escapeCharClassEx); console.timeEnd("char class long ex"); console.time("serial long"); replace_many(text_long, escapeSerial); console.timeEnd("serial long"); };
検証結果
Chrome40で走らせた結果、以下のような結果になりました。
test name | time |
---|---|
char class short | 7.573ms |
char class short ex | 4.213ms |
serial short | 1.972ms |
char class long | 296.516ms |
char class long ex | 264.621ms |
serial long | 128.274ms |
単純なマッチで4回replaceしたバージョン(escapeSerial
)が、短い文字列では4倍程度、長い文字列だと2倍ちょっとぐらい速いという結果でした。
いや、ちょっとおかしい
文字クラスを使った場合の関数は、単にマッチ後のコールバック関数呼び出しのオーバヘッドが入るだけではあるまいか、ということでescapeSerial
における各replace処理を関数で置換するように変えたら、今度は4回呼び出しのほうがコールバックの回数が増えたぶん、少しだけ遅かったです。
test name | time |
---|---|
char class short | 5.004ms |
serial short | 5.218ms |
char class long | 2559.319ms |
serial long | 3020.997ms |
ということで、
- 文字クラスで複数文字を一回でマッチ&置換させても、それぞれの文字で一回ずつ置換しても速度は殆ど変わらない
- しかしそれぞれの置換でコールバック関数を呼ぶとパフォーマンスは二倍ぐらい遅くなる
- 各関数の呼び出しの中でテーブル参照すると更に遅くなる
みたいです。
たぶん1については、4×1と1×4程度の違いなんだと思います。