JavaScriptにはJavaで言うところのString.startsWithがないです。みんなどうやっているかというと、
if (str.indexOf('prefix') === 0) {
console.log('prefix found');
}
みたいな感じのコードをよく見ます。AngularJSのソースでも見ました。でも、これって良く考えると無駄ですよね。indexOfというのは文字列を最初から最後までなめて、一致する位置を探すのです。見つかれば一発ですが、見つからなければ最後まで行って-1を返します。本来、prefixを判定するのなら最初だけ検査すればいいのです。
str.substring(0, 'prefix'.length) === 'prefix'
という書き方も見ますが、これもいまひとつです。substringは新たに文字列を作ってしまうので。結論としては、
str.lastIndexOf('prefix', 0) === 0
が一番いいと思います。lastIndexOfは文字列の後ろから検査するのですが、検査開始位置を0にしているので、判定するのは一回だけです。Javaの内部実装には詳しくないですが、String.startsWithも同じような実装でしょう。
さて、これが本当かを確かめるために、benchmark.jsでベンチマークを書こうかと思いました。いや、せっかくなのでjsperfで書いてみようと思って、書き始めたら、あれ、既に存在する。世の中同じこと考える人はいますね。
http://jsperf.com/string-startswith/3
最後の一つのテストケースは私が追加したものです。 想定通り、lastIndexOfが速いです。ん?しかし、Chromeだとcompiled regexの方が速い。いや、そんなはずは、、。compiled regexが速いのは事実ですが、lastIndexOfが遅いのです。こんな単純な関数でChromeがFirefoxより遅いのは想定外でした。
Chromeで遅いということは、v8が遅いのかと思って、node.jsでもテストしてみました。
https://gist.github.com/dai-shi/4950506
案の定、node.jsでも同様の結果です。 それでも、私はlastIndexOfを使うことをおすすめします。 v8で遅いのはv8が直るべきです。ところで、これはissueになっているのかな?
http://code.google.com/p/v8/issues/list
どうやらなっていないようです。ちょっとソースを覗いてみますか。v8はsubversionです。bleeding edgeブランチが最新のようです。
http://v8.googlecode.com/svn/branches/bleeding_edge/
checkoutしてStringLastIndexOfのソースコードを追ってみましたが、特に単純な問題があるようには見えません。久しぶりのCのソースで頭が疲れます。そりゃそうですよね、天下のGoogleが作っているものがそんな自明なバグを残しているわけないですよね。というわけで、しばらくは様子をみることにします。
ところで、jsperf.comにstring-startswithがあったのが悔しかったので、真似してstring-endswithを作ってみました。
http://jsperf.com/string-endswith
傾向はstartsWithと同様です。ここまで読んでくれたみなさん、suffixチェックするときに、間違ってもnaive indexOfのような書き方はしないように。
匿名
lastIndexOfではprefix文字列が後ろにも出現した場合に、startWithと同じ結果になりませんよ。
dai-shiから匿名への返信
第二引数に0を指定すれば大丈夫です。それを忘れたら、その通りです。
匿名2
「接頭辞を調べる」という文脈でlastIndexOfなんか使われたら発狂しますな
dai-shiから匿名2への返信
そういうときは仕方ないので、
String.prorotype.startsWith = function(str) {
return this.lastIndexOf(str, 0) === 0;
};
とでもするしかないですね。
関数呼び出しのコストとsubstring等で文字列生成のコストの比較は分かりませんが。
通りすがり
ES6から使えます
http://kangax.github.io/es5-compat-table/es6/#String.prototype.startsWith