C#の String.StartsWith
と同等のものをJavaScriptで書くにはどうすればよいですか。
var haystack = 'hello world';
var needle = 'he';
haystack.startsWith(needle) == true
注:これは古い質問であり、コメントECMAScript 2015(ES6)で指摘されているように .startsWith
メソッドが導入されました。しかし、この更新を書いている時点で(2015) ブラウザのサポートは完全には程遠い 。
ECMAScript 6の String.prototype.startsWith()
メソッドを使用できますが、 すべてのブラウザでまだサポートされているわけではありません 。あなたはそれをサポートしていないブラウザでそれを追加するためにシム/ポリフィルを使用したいと思うでしょう。 仕様に記載されているすべての詳細 に準拠する実装を作成するのは、少し複雑です。あなたが忠実なシムが欲しいならば、どちらかを使ってください:
String.prototype.startsWith
shim 、またはString.prototype.startsWith
を含む、できるだけ多くのES6仕様をシム化します。メソッドをシムしたら(または既にそれを持っているブラウザとJavaScriptエンジンだけをサポートしているのなら)、このようにそれを使うことができます:
"Hello World!".startsWith("He"); // true
var haystack = "Hello world";
var prefix = 'orl';
haystack.startsWith(prefix); // false
.lastIndexOf
を使用したもう1つの方法
haystack.lastIndexOf(needle, 0) === 0
これはhaystack
のインデックス0
から始まるneedle
の出現をhaystack
を通して逆方向に調べます。つまり、haystack
がneedle
で始まっているかどうかだけをチェックします。
原則として、これは他のいくつかのアプローチよりもパフォーマンス上の利点があります。
haystack
全体を検索するわけではありません。data.substring(0, input.length) === input
ヘルパー関数がなければ、正規表現の .test
メソッドを使うだけです。
/^He/.test('Hello world')
ハードコードされたものではなく動的な文字列でこれを行うには(文字列に正規表現の制御文字が含まれないと仮定して):
new RegExp('^' + needle).test(haystack)
あなたはチェックアウトするべきです JavascriptにRegExp.escape関数がありますか? 正規表現の制御文字が文字列に現れる可能性がある場合。
最良の解決策:
function startsWith(str, Word) {
return str.lastIndexOf(Word, 0) === 0;
}
startsWith("aaa", "a")
true
startsWith("aaa", "ab")
false
startsWith("abc", "abc")
true
startsWith("abc", "c")
false
startsWith("abc", "a")
true
startsWith("abc", "ba")
false
startsWith("abc", "ab")
true
そしてここに endsWith があります。
function endsWith(str, Word) {
return str.indexOf(Word, str.length - Word.length) !== -1;
}
それをStringにプロトタイプ化することを好む人のために:
String.prototype.startsWith || (String.prototype.startsWith = function(Word) {
return this.lastIndexOf(Word, 0) === 0;
});
String.prototype.endsWith || (String.prototype.endsWith = function(Word) {
return this.indexOf(Word, this.length - Word.length) !== -1;
});
使用法:
"abc".startsWith("ab")
true
"c".ensdWith("c")
true
私はちょうどこれについて私の意見を加えたいと思いました。
私たちはちょうどこのように使うことができると思います:
var haystack = 'hello world';
var needle = 'he';
if (haystack.indexOf(needle) == 0) {
// Code if string starts with this substring
}
これは、CMSのソリューションに対する小さな改善です。
if(!String.prototype.startsWith){
String.prototype.startsWith = function (str) {
return !this.indexOf(str);
}
}
"Hello World!".startsWith("He"); // true
var data = "Hello world";
var input = 'He';
data.startsWith(input); // true
将来のブラウザでネイティブコードで実装されている場合や、別のライブラリで実装されている場合に、その関数が既に存在するかどうかを確認します。例えば、プロトタイプライブラリはすでにこの機能を実装しています。
!
の使用は、=== 0
よりもわずかに速くて簡潔ですが、読みやすくはありません。
underscore.string.js もチェックしてください。 startsWith
メソッドを含む、たくさんの便利な文字列テストや操作メソッドが付属しています。ドキュメントから:
startsWith
_.startsWith(string, starts)
このメソッドは
string
がstarts
で始まるかどうかをチェックします。_("image.gif").startsWith("image") => true
私は最近自分自身に同じ質問をしました。
可能な解決策は複数あります。有効な解決策は3つあります。
s.indexOf(starter) === 0
s.substr(0,starter.length) === starter
s.lastIndexOf(starter, 0) === 0
(Mark Byersの answer を見た後に追加されました)ループを使う:
function startsWith(s,starter) {
for (var i = 0,cur_c; i < starter.length; i++) {
cur_c = starter[i];
if (s[i] !== starter[i]) {
return false;
}
}
return true;
}
私はループを利用する最後の解決策に出会ったことがありません。
驚くべきことに、このソリューションは最初の3つを大幅に上回っています。
これが私がこの結論に達するために行ったjsperfテストです: http://jsperf.com/startswith2/2
平和
ps:ecmascript 6(harmony)では、文字列用のネイティブなstartsWith
メソッドが導入されました。
最初のバージョンにこのように必要なメソッドを含めることを考えていたとしたら、どれだけの時間が節約できたかと思います。
更新
Steveが指摘したように(この答えに対する最初のコメント)、与えられたprefixが文字列全体より短い場合、上記のカスタム関数はエラーを投げます。彼はそれを修正し、 http://jsperf.com/startswith2/4 で表示できるループ最適化を追加しました。
Steveが含んでいた2つのループ最適化があることに注意してください、2つのうちの最初のものはより良い性能を示しました、それで私はそのコードを以下に投稿します:
function startsWith2(str, prefix) {
if (str.length < prefix.length)
return false;
for (var i = prefix.length - 1; (i >= 0) && (str[i] === prefix[i]); --i)
continue;
return i < 0;
}
これはとてもポピュラーなので、ECMA 6にこのメソッドの実装があり、それに備えて将来の問題や涙を防ぐために「公式の」ポリフィルを使うべきであることを指摘する価値があると思います。
幸いなことに、Mozillaのエキスパートは私たちに1つを提供してくれます:
https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position) {
position = position || 0;
return this.indexOf(searchString, position) === position;
};
}
これにはECMA 6への移行時に正常に無視されるという利点があります。
最善の解決策は、ライブラリ呼び出しの使用をやめて、2つの配列で作業していることを認識することです。手作業による実装は、私がここで見た他のどのソリューションよりも短く、しかも高速です。
function startsWith2(str, prefix) {
if (str.length < prefix.length)
return false;
for (var i = prefix.length - 1; (i >= 0) && (str[i] === prefix[i]); --i)
continue;
return i < 0;
}
パフォーマンスの比較(成功と失敗)については、 http://jsperf.com/startswith2/4 を参照してください。 (私の頭を悩ませている可能性のある最新バージョンを必ずチェックしてください。)
私はちょうどこの文字列ライブラリについて学びました:
Jsファイルをインクルードしてから、次のようにS
変数を使用します。
S('hi there').endsWith('hi there')
インストールすることでNodeJSで使用することもできます。
npm install string
それからS
変数としてそれを必要とします:
var S = require('string');
Webページには、代替の文字列ライブラリへのリンクもあります。
var str = 'hol';
var data = 'hola mundo';
if (data.length >= str.length && data.substring(0, str.length) == str)
return true;
else
return false;
基本的に、長い針が長い干し草の山の中にあり、最後の文字を除いて非常に似ているかどうかをすばやく見つける方法が必要でした。
これは、各関数(splice、substring、startsWithなど)が1.000.0001文字のhaystack文字列(nestedString
)と偽りまたは真実の針に対してfalseとtrueの両方を返すときにテストするコードです。 1.000.000文字の文字列(それぞれtestParentStringFalse
およびtestParentStringTrue
):
// nestedString is made of 1.000.001 '1' repeated characters.
var nestedString = '...'
// testParentStringFalse is made of 1.000.000 characters,
// all characters are repeated '1', but the last one is '2',
// so for this string the test should return false.
var testParentStringFalse = '...'
// testParentStringTrue is made of 1.000.000 '1' repeated characters,
// so for this string the test should return true.
var testParentStringTrue = '...'
// You can make these very long strings by running the following bash command
// and edit each one as needed in your editor
// (NOTE: on OS X, `pbcopy` copies the string to the clipboard buffer,
// on Linux, you would probably need to replace it with `xclip`):
//
// printf '1%.0s' {1..1000000} | pbcopy
//
function testString() {
let dateStart
let dateEnd
let avg
let count = 100000
const falseResults = []
const trueResults = []
/* slice */
console.log('========> slice')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.slice(0, testParentStringFalse.length) === testParentStringFalse
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
falseResults[falseResults.length] = {
label: 'slice',
avg
}
console.log(`testString() slice = false`, res, 'avg: ' + avg + 'ms')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.slice(0, testParentStringTrue.length) === testParentStringTrue
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
trueResults[trueResults.length] = {
label: 'slice',
avg
}
console.log(`testString() slice = true`, res, 'avg: ' + avg + 'ms')
console.log('<======== slice')
console.log('')
/* slice END */
/* lastIndexOf */
console.log('========> lastIndexOf')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.lastIndexOf(testParentStringFalse, 0) === 0
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
falseResults[falseResults.length] = {
label: 'lastIndexOf',
avg
}
console.log(`testString() lastIndexOf = false`, res, 'avg: ' + avg + 'ms')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.lastIndexOf(testParentStringTrue, 0) === 0
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
trueResults[trueResults.length] = {
label: 'lastIndexOf',
avg
}
console.log(`testString() lastIndexOf = true`, res, 'avg: ' + avg + 'ms')
console.log('<======== lastIndexOf')
console.log('')
/* lastIndexOf END */
/* indexOf */
console.log('========> indexOf')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.indexOf(testParentStringFalse) === 0
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
falseResults[falseResults.length] = {
label: 'indexOf',
avg
}
console.log(`testString() indexOf = false`, res, 'avg: ' + avg + 'ms')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.indexOf(testParentStringTrue) === 0
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
trueResults[trueResults.length] = {
label: 'indexOf',
avg
}
console.log(`testString() indexOf = true`, res, 'avg: ' + avg + 'ms')
console.log('<======== indexOf')
console.log('')
/* indexOf END */
/* substring */
console.log('========> substring')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.substring(0, testParentStringFalse.length) === testParentStringFalse
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
falseResults[falseResults.length] = {
label: 'substring',
avg
}
console.log(`testString() substring = false`, res, 'avg: ' + avg + 'ms')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.substring(0, testParentStringTrue.length) === testParentStringTrue
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
trueResults[trueResults.length] = {
label: 'substring',
avg
}
console.log(`testString() substring = true`, res, 'avg: ' + avg + 'ms')
console.log('<======== substring')
console.log('')
/* substring END */
/* startsWith */
console.log('========> startsWith')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.startsWith(testParentStringFalse)
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
falseResults[falseResults.length] = {
label: 'startsWith',
avg
}
console.log(`testString() startsWith = false`, res, 'avg: ' + avg + 'ms')
dateStart = +new Date()
var res
for (let j = 0; j < count; j++) {
res = nestedString.startsWith(testParentStringTrue)
}
dateEnd = +new Date()
avg = (dateEnd - dateStart)/count
trueResults[trueResults.length] = {
label: 'startsWith',
avg
}
console.log(`testString() startsWith = true`, res, 'avg: ' + avg + 'ms')
console.log('<======== startsWith')
console.log('')
/* startsWith END */
falseResults.sort((a, b) => a.avg - b.avg)
trueResults.sort((a, b) => a.avg - b.avg)
console.log('false results from fastest to slowest avg:', falseResults)
console.log('true results from fastest to slowest avg:', trueResults)
}
Chrome 75、Firefox 67、Safari 12およびOpera 62でこのベンチマークテストを実行しました。
このマシンにはEdgeとIEがないため、これらは含めませんでしたが、もし誰かがEdgeと少なくともIE 9に対してスクリプトを実行し、出力をここで共有したい場合は、結果を見て非常に興味があります。
3つの長い文字列を再作成し、スクリプトをファイルに保存する必要があることを忘れないでください。ブラウザのコンソールでコピー/貼り付けを行うと、各文字列の長さが1.000.000を超えるため、スクリプトがブロックされます。
出力は次のとおりです。
Chrome 75(substring
勝):
false results from fastest to slowest avg:
1) {"label":"substring","avg":0.08271}
2) {"label":"slice","avg":0.08615}
3) {"label":"lastIndexOf","avg":0.77025}
4) {"label":"indexOf","avg":1.64375}
5) {"label":"startsWith","avg":3.5454}
true results from fastest to slowest avg:
1) {"label":"substring","avg":0.08213}
2) {"label":"slice","avg":0.08342}
3) {"label":"lastIndexOf","avg":0.7831}
4) {"label":"indexOf","avg":0.88988}
5) {"label":"startsWith","avg":3.55448}
Firefox 67(indexOf
が勝ちます):
false results from fastest to slowest avg
1) {"label":"indexOf","avg":0.1807}
2) {"label":"startsWith","avg":0.74621}
3) {"label":"substring","avg":0.74898}
4) {"label":"slice","avg":0.78584}
5) {"label":"lastIndexOf","avg":0.79668}
true results from fastest to slowest avg:
1) {"label":"indexOf","avg":0.09528}
2) {"label":"substring","avg":0.75468}
3) {"label":"startsWith","avg":0.76717}
4) {"label":"slice","avg":0.77222}
5) {"label":"lastIndexOf","avg":0.80527}
Safari 12(slice
は偽の結果で勝ち、startsWith
は真の結果で勝ちます。また、Safariはテスト全体を実行する合計時間の面で最速です):
false results from fastest to slowest avg:
1) "{\"label\":\"slice\",\"avg\":0.0362}"
2) "{\"label\":\"startsWith\",\"avg\":0.1141}"
3) "{\"label\":\"lastIndexOf\",\"avg\":0.11512}"
4) "{\"label\":\"substring\",\"avg\":0.14751}"
5) "{\"label\":\"indexOf\",\"avg\":0.23109}"
true results from fastest to slowest avg:
1) "{\"label\":\"startsWith\",\"avg\":0.11207}"
2) "{\"label\":\"lastIndexOf\",\"avg\":0.12196}"
3) "{\"label\":\"substring\",\"avg\":0.12495}"
4) "{\"label\":\"indexOf\",\"avg\":0.33667}"
5) "{\"label\":\"slice\",\"avg\":0.49923}"
Opera 62(substring
が勝ちます。結果はChromeに似ており、OperaはChromiumとBlinkに基づいているので驚きません):
false results from fastest to slowest avg:
{"label":"substring","avg":0.09321}
{"label":"slice","avg":0.09463}
{"label":"lastIndexOf","avg":0.95347}
{"label":"indexOf","avg":1.6337}
{"label":"startsWith","avg":3.61454}
true results from fastest to slowest avg:
1) {"label":"substring","avg":0.08855}
2) {"label":"slice","avg":0.12227}
3) {"label":"indexOf","avg":0.79914}
4) {"label":"lastIndexOf","avg":1.05086}
5) {"label":"startsWith","avg":3.70808}
すべてのブラウザーには独自の実装の詳細があります(ChromeのChromiumとBlinkに基づくOperaを除く)。
もちろん、さまざまなユースケースでさらにテストを実行することができ、実行する必要があります(たとえば、haystackと比較してneedleが本当に短い場合、haystackがneedleより短い場合など)。しかし、私の場合、非常に長い文字列とここで共有したかった。
私はjavascriptについてはわかりませんが、TypeScriptでは次のようなことをしました
var str = "something";
(<String>str).startsWith("some");
Jsでも動作するはずです。私はそれが役立つことを願っています!
ここでの答えに基づいて、JSPerfテストに基づいて最高のパフォーマンスが得られるように(そして私が言える限り機能的には完成しているので)、これは私が現在使用しているバージョンです。
if(typeof String.prototype.startsWith != 'function'){
String.prototype.startsWith = function(str){
if(str == null) return false;
var i = str.length;
if(this.length < i) return false;
for(--i; (i >= 0) && (this[i] === str[i]); --i) continue;
return i < 0;
}
}
これはここからのstartsWith2に基づいていました: http://jsperf.com/startswith2/6 。小さなパフォーマンスの向上のために小さなTweakを追加し、それ以来、比較文字列がnullまたは未定義であることのチェックも追加し、CMSの回答の手法を使用してStringプロトタイプに追加するように変換しました。
この実装はこの Mozilla Developer Network のページで言及されている "position"パラメータをサポートしていませんが、それでもECMAScriptの提案の一部ではないようです。