JavaScriptのlastIndexOf():最後の区切り文字を探して文字列を分ける
String.prototype.lastIndexOf()で最後に現れる文字列の位置を調べる方法を解説。引数・戻り値、ブラウザーとNode.jsの実行手順、区切り文字の検索例、未検出やUnicodeの注意点を学べます。
目的:最後の区切りを基準にデータを取り出す
team:service:workerという識別子から、最後の名前であるworkerと、その前の所属部分を取り出したいことがあります。区切り文字が何個あるか決まっていなくても、最後の区切りの位置が分かれば処理できます。
String.prototype.lastIndexOf()は、指定した文字列が最後に現れる位置を返すメソッドです。単なる有無の確認に加えて、「どこで分けるか」という判断材料を得られます。検索対象の文字列は変更しません。
この記事では、最後の区切り文字で識別子を分ける例を中心に、検索範囲の指定や失敗しやすい条件を確認します。配列にも同名のメソッドがありますが、ここで扱う対象は文字列です。
動作環境と対応バージョン
2026年11月11日時点で、MDNはこのメソッドを広く利用可能な機能とし、2015年7月以降のブラウザー間で利用できると説明しています。現在のChrome、Edge、Firefox、Safariで使用できます。この日付は各ブラウザーへの初回導入日を表すものではありません。MDNのlastIndexOf()
ブラウザー専用のAPIではなく、JavaScriptの組み込みメソッドなのでNode.jsでも動作します。掲載コードの実行対象はNode.js 22以降とします。22で新しく追加された機能という意味ではなく、この記事で手順の対象にするバージョンです。追加パッケージや実験用フラグは必要ありません。
ブラウザーで実行する
空のタブなどで開発者ツールを開き、Consoleに後述するJavaScriptコード全体を貼り付けて実行します。出力の末尾に開発者ツールが表示するundefinedは、コードのログとは別の評価結果です。DOM操作も通信もないため、HTMLファイルやサーバーの用意は不要です。
Node.jsで実行する
Node.jsが使えるターミナルで、まずバージョンを確認します。
node --versionコードをUTF-8のlast-index-demo.jsに保存し、そのファイルがあるディレクトリーで次を実行します。
node last-index-demo.jsファイル名をnodeに渡す方法は、Node.js公式のスクリプト実行手順に沿っています。以下の例はブラウザーとNode.jsで同じコードを使えます。
引数と戻り値を理解する
呼び出し方は次のとおりです。
text.lastIndexOf(searchString);
text.lastIndexOf(searchString, position);searchStringは探したい部分文字列です。正規表現のパターンを指定する引数ではありません。positionは省略可能で、一致の開始位置の上限を指定します。省略時は文字列全体から最後の一致を探します。- 戻り値は、見つかった部分文字列の開始位置を表す整数です。先頭は
0で、見つからなければ-1です。
重要なのは、positionが「一致全体の末尾の上限」ではないことです。例えば"ababa".lastIndexOf("aba", 2)は2を返します。位置2で始まるabaは、位置4まで続いていても一致します。
positionが文字列の長さより大きければ全体が検索対象になります。負数は0と同様に扱われ、末尾から数える指定にはなりません。検索は大文字と小文字を区別します。これらはMDNの引数と動作の説明で確認できます。
実践:最後の区切りで識別子を分ける
次のコード全体をコピーしてください。独自関数splitAtLast()は、検索した位置を使って前半と後半を返します。外部データや他のファイルには依存しません。
(() => {
function splitAtLast(text, separator) {
if (typeof text !== "string" || typeof separator !== "string") {
throw new TypeError("文字列を指定してください");
}
if (separator.length === 0) {
throw new RangeError("区切り文字を空にできません");
}
const index = text.lastIndexOf(separator);
if (index === -1) {
return { found: false, before: "", after: text };
}
return {
found: true,
before: text.slice(0, index),
after: text.slice(index + separator.length),
};
}
const samples = [
["team:service:worker", ":"],
["worker", ":"],
[":worker", ":"],
["team:", ":"],
["team::service::worker", "::"],
];
for (const [text, separator] of samples) {
const result = splitAtLast(text, separator);
console.log(JSON.stringify(result));
}
try {
splitAtLast("worker", "");
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
})();期待出力は次のとおりです。実行結果を取得したものではなく、コードと仕様から導いた出力です。
{"found":true,"before":"team:service","after":"worker"}
{"found":false,"before":"","after":"worker"}
{"found":true,"before":"","after":"worker"}
{"found":true,"before":"team","after":""}
{"found":true,"before":"team::service","after":"worker"}
RangeError: 区切り文字を空にできません未検出と空の前半を区別する理由
区切りがない場合と、区切りが先頭にある場合では、どちらもbeforeは空文字列です。そこでfoundを返し、「見つかったが前半が空」と「見つからなかった」を区別しています。
また、区切りが::のように複数文字でも、separator.lengthだけ進めて後半を切り出せます。常にindex + 1とすると、区切りの一部が後半に残ってしまいます。
未検出時に元の文字列をafterへ返すのは、この独自関数で決めた方針です。業務上、区切りが必須なら、その分岐で例外を投げる設計に変更してください。
検索位置を指定して前の一致へ戻る
最後の一致より前を探したい場合は、その開始位置から1を引いて再検索します。次も単独で実行できます。
(() => {
const text = "ababa";
const last = text.lastIndexOf("aba");
const previous = last > 0
? text.lastIndexOf("aba", last - 1)
: -1;
console.log(last);
console.log(previous);
console.log(text.lastIndexOf("aba", 2));
console.log(text.lastIndexOf("a", -1));
})();期待出力です。
2
0
2
0この例では、位置0と2で始まる一致が重なっています。開始位置を基準に移動するので、重なる一致も探せます。
last > 0の条件にも意味があります。最後の一致が位置0だったとき、-1を渡しても検索位置は0として扱われるため、同じ一致を再び取得します。繰り返し検索する実装では、位置0に到達した時点で終了させてください。
失敗例と制約
戻り値をそのまま条件式に入れない
if (text.lastIndexOf(":"))では、位置0が偽になり、未検出の-1が真になります。検索結果を変数に保存し、見つかったかどうかはindex !== -1で判定します。実践コードで明示的に比較しているのは、この取り違えを避けるためです。
空文字列と引数の省略に注意する
空文字列を探すと、通常の区切り検索とは異なり、例えば"abc".lastIndexOf("")は3を返します。実践コードでは、区切りとして意味が曖昧になるため独自に禁止しました。
また、検索語を省略したりundefinedを渡したりすると、文字列"undefined"が検索されます。APIから受け取った値は、意図する文字列か確認してから渡しましょう。実践コードの型チェックは、このような暗黙変換による見落としを減らすためのものです。
位置は見た目の文字数ではない
JavaScriptの文字列の添字はUTF-16コード単位です。例えば"😀:x".lastIndexOf(":")は2になります。絵文字が見た目では1文字でも、ここでは2単位を使うためです。MDNのStringと文字の扱い
取得した位置を同じ文字列のslice()へ渡す用途には適しています。一方、画面上の「何文字目」という案内へ、そのまま転用するのは避けてください。
同期処理と用途の境界
lastIndexOf()は同期処理で、Promiseを返しません。awaitは不要です。実践コードの入力検証が投げる例外も、その場のtry...catchで受け取れます。
この関数は、区切り文字を含めてよい値や、引用符で囲まれた範囲などを理解しません。CSVやURLのように独自の構文規則があるデータ全体を、最後の記号だけで解析する設計には向きません。
活用事例と設計の改善案
使いやすいのは、アプリケーション側で区切り規則を決めた識別子です。最後の区切りより前をグループ名、後ろを表示名として扱えば、階層が増えても同じ処理を使えます。
実装を改善するなら、先に「区切りなしを許すか」「前半・後半が空でもよいか」「値自体に区切りを含めてよいか」を決めてください。検索方法だけでなく入力規則を明確にすることで、不正データを正常な識別子として扱う問題を防げます。掲載コードでは境界条件を見える形で残しているため、その規則を追加する出発点にできます。
公式参考資料
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。