JavaScript

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が使えるターミナルで、まずバージョンを確認します。

Shell
node --version

コードをUTF-8のlast-index-demo.jsに保存し、そのファイルがあるディレクトリーで次を実行します。

Shell
node last-index-demo.js

ファイル名をnodeに渡す方法は、Node.js公式のスクリプト実行手順に沿っています。以下の例はブラウザーとNode.jsで同じコードを使えます。

引数と戻り値を理解する

呼び出し方は次のとおりです。

JavaScript
text.lastIndexOf(searchString);
text.lastIndexOf(searchString, position);
  • searchStringは探したい部分文字列です。正規表現のパターンを指定する引数ではありません。
  • positionは省略可能で、一致の開始位置の上限を指定します。省略時は文字列全体から最後の一致を探します。
  • 戻り値は、見つかった部分文字列の開始位置を表す整数です。先頭は0で、見つからなければ-1です。

重要なのは、positionが「一致全体の末尾の上限」ではないことです。例えば"ababa".lastIndexOf("aba", 2)は2を返します。位置2で始まるabaは、位置4まで続いていても一致します。

positionが文字列の長さより大きければ全体が検索対象になります。負数は0と同様に扱われ、末尾から数える指定にはなりません。検索は大文字と小文字を区別します。これらはMDNの引数と動作の説明で確認できます。

実践:最後の区切りで識別子を分ける

次のコード全体をコピーしてください。独自関数splitAtLast()は、検索した位置を使って前半と後半を返します。外部データや他のファイルには依存しません。

JavaScript
(() => {
  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}`);
  }
})();

期待出力は次のとおりです。実行結果を取得したものではなく、コードと仕様から導いた出力です。

CODE
{"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を引いて再検索します。次も単独で実行できます。

JavaScript
(() => {
  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));
})();

期待出力です。

CODE
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のように独自の構文規則があるデータ全体を、最後の記号だけで解析する設計には向きません。

活用事例と設計の改善案

使いやすいのは、アプリケーション側で区切り規則を決めた識別子です。最後の区切りより前をグループ名、後ろを表示名として扱えば、階層が増えても同じ処理を使えます。

実装を改善するなら、先に「区切りなしを許すか」「前半・後半が空でもよいか」「値自体に区切りを含めてよいか」を決めてください。検索方法だけでなく入力規則を明確にすることで、不正データを正常な識別子として扱う問題を防げます。掲載コードでは境界条件を見える形で残しているため、その規則を追加する出発点にできます。

公式参考資料

PREFERRED SOURCES

Googleの優先するニュース提供元にSEの部屋を追加

Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。

Googleの設定画面で確認する 新しいタブで開きます

候補にSEの部屋が表示されない場合は、まだ追加できません。

y.
WRITTEN BY

y_ymo10

SEの部屋で、HTML・CSSからJavaScript・React・Next.jsまで、Web制作の開発ノートを公開しています。

ほかの開発ノートを読む →