やあ。最近、コードレビューをしていて「あ、これ惜しいな」「もっと綺麗に書けるのに」と思うことがよくあるんだよね。特に文字列操作あたりは、なんとなく`indexOf()`や正規表現を引っ張り出してきて、動くには動くけど……というコードに出会うことが多い。
今回は、文字列の末尾から前方に向かって特定のキーワードを探す`String.prototype.lastIndexOf()`を取り上げる。
「え、今更 `lastIndexOf`?」って思ったかい?
実はこの地味なメソッド、フロントエンドの実務で「ファイルの拡張子をスマートに抜きたい」「URLのパスから最後のセグメントだけを正確に切り出したい」といった場面に直面したとき、知っているかいないかでコードの美しさとパフォーマンスが大きく変わる隠れた実力者なんだ。
今日は、こいつの仕様の裏側から、現場で即戦力になるテクニックまで、シニアの視点でたっぷりと解説していこう。
—
1. `lastIndexOf()` ってぶっちゃけどんな仕様?
`lastIndexOf()` は、指定した文字列を「右から左へ(末尾から前方へ)」検索し、最初に見つかった位置(インデックス)を数値で返すメソッドだ。
基本の構文はこうだ。
str.lastIndexOf(searchValue[, fromIndex])
- `searchValue`: 探したい文字列。
- `fromIndex`(オプション): 検索を開始するインデックスの上限。デフォルトは文字列の長さ(`str.length – 1`)。
お馴染みの `indexOf()` が「左から右」に探すのに対して、`lastIndexOf()` は「右から左」に探す。ただ、ここで一つ初心者がハマりがちな重要な仕様の罠がある。
「右から左へ検索する」とは言うものの、返されるインデックスの番号自体は、あくまで「左端を0とした通常のインデックス」であるということだ。
これ、言葉だけだとピンとこないよな。ちょっと次のコードを見てほしい。
const text = “frontend.development.js”;
// 右から ‘.’ を探す
console.log(text.lastIndexOf(‘.’)); // 19
末尾の `.js` の手前にあるドットの位置を綺麗に一発で射抜いている。右から探し始めたけれど、返ってきた `19` という数字は、左端の `f` を `0` と数えたときの位置だ。ここを勘違いして「右からの距離」だと誤解していると、後続の `slice` 処理などで盛大にバグるから注意してくれ。
—
2. ブラウザの裏側で何が起きているのか?(V8エンジンの視点)
フロントエンドエンジニアとして一歩先に行くなら、「JavaScriptのメソッドが裏側でどう動いているか」のイメージを持っておくべきだ。
V8などの近代的なJavaScriptエンジンにおいて、Stringオブジェクトは内部的にはUTF-16(またはラテン1文字なら1バイト)の連続したメモリ領域(バッファ)として保持されている。
`indexOf()` は、文字列の先頭(インデックス0)から1文字ずつインクリメントしながらメモリをスキャンしていく。一方、`lastIndexOf()` は、指定された `fromIndex` からデクリメント(減算)しながらメモリを逆向きにスキャンしていくんだ。
ここで実務的なパフォーマンスの話をしよう。
「検索対象の文字列がめちゃくちゃ長い(例えば数万文字のHTMLやログデータ)」かつ「探したいキーワードが末尾の方にある」と分かっている場合、`indexOf()` を使うと先頭からスキャンし始めるため無駄な走査が発生する。
しかし、`lastIndexOf()` であれば、末尾付近の `fromIndex` を指定してやれば、一瞬でターゲットにヒットさせることができる。
逆に、ターゲットが先頭付近にあると分かっているのに `lastIndexOf()` を使うと、無駄に末尾から遡るハメになる。「どこに目的のデータがありそうか」というドメイン知識に合わせて使い分けるのが、プロの選択というわけだ。
—
3. 現場で使える!実践的なコードパターン
理屈はこれくらいにして、実際のフロントエンド開発で「おっ、これ使えるな」と思えるユースケースを3つ紹介しよう。そのままエディタにコピペして動かせるようにしてある。
パターンA:ファイル名から拡張子を安全に抜き出す
ユーザーがアップロードしたファイル名(例: `my.awesome.profile.image.png`)から拡張子を抽出する処理によく使われる。
/
- ファイル名から拡張子を取得する関数
- @param {string} filename
- @returns {string} 拡張子(ドットを含まない)
/
function getFileExtension(filename) {
// 最後に現れるドットの位置を探す
const lastDotIndex = filename.lastIndexOf(‘.’);
// ドットが見つからない場合や、ファイル名の先頭にある場合(隠しファイル等)
if (lastDotIndex === -1 || lastDotIndex === 0) {
return ”;
}
// ドットの次から末尾までを切り出す
return filename.slice(lastDotIndex + 1);
}
// 実行例
console.log(getFileExtension(‘archive.tar.gz’)); // “gz”
console.log(getFileExtension(‘index.html’)); // “html”
console.log(getFileExtension(‘README’)); // “”
正規表現を使う方法もあるが、単純な文字列の末尾からの切り出しであれば、`lastIndexOf` の方が可読性が高く、意図せぬReDoS(正規表現DoS攻撃)のリスクも排除できる。こういう地味で堅牢なコードが、プロダクション環境では重宝されるんだ。
パターンB:URLのパスから最後のセグメント(IDなど)を取得する
SPA(Single Page Application)のルーティングやAPIのレスポンス処理で、`/api/v1/users/42` のようなRESTfulなパスから最後のID(`42`)を取り出したいときがある。
/
- URLパスの最後のセグメントを取得する
- @param {string} path
- @returns {string}
/
function getLastPathSegment(path) {
// 末尾のトレイリングスラッシュ(末尾のスラッシュ)を考慮して正規化
const trimmedPath = path.endsWith(‘/’) ? path.slice(0, -1) : path;
const lastSlashIndex = trimmedPath.lastIndexOf(‘/’);
if (lastSlashIndex === -1) {
return trimmedPath;
}
return trimmedPath.slice(lastSlashIndex + 1);
}
// 実行例
console.log(getLastPathSegment(‘/api/v1/products/1089/’)); // “1089”
console.log(getLastPathSegment(‘/dashboard’)); // “dashboard”
末尾にスラッシュがつくケースを `endsWith` でケアしつつ、`lastIndexOf(‘/’)` で最後のスラッシュの位置を特定する。実務のAPI処理では定番のイディオムだね。
パターンC:第2引数(fromIndex)を活かした「特定の位置より前」からの検索
`lastIndexOf` の真骨頂は、第2引数の `fromIndex` を使いこなすことにある。例えば、「特定のキーワードよりさらに前にある、最後のスラッシュを見つけたい」といった複雑なパース処理で威力を発揮する。
const logMessage = “[INFO] 2023-10-25 /var/www/html/index.js – Process completed successfully”;
// 「index.js」という文字列の位置をまず特定
const targetIndex = logMessage.indexOf(‘index.js’);
if (targetIndex !== -1) {
// 「index.js」の出現位置よりも「前」の範囲だけで、最後のスラッシュを探す
const lastSlashBeforeIndex = logMessage.lastIndexOf(‘/’, targetIndex);
console.log(lastSlashBeforeIndex); // /var/www/html/ の最後のスラッシュの位置を指す
// パス部分だけを切り出す
const extractedPath = logMessage.slice(lastSlashBeforeIndex – 3, targetIndex); // 簡易的な切り出し
console.log(extractedPath); // “html/” あたりが取れる
}
このように、`indexOf` で見つけた位置を `lastIndexOf` の第2引数に渡すことで、「この地点から左へ遡った最初の一致」をピンポイントで狙い撃ちできる。このテクニックを知っていると、独自の簡易パーサーやログ解析ツールを自作するときにめちゃくちゃ役立つはずだ。
—
シニアからのまとめ
JavaScriptの標準メソッドは、どれも一見するとシンプルで、「ググれば使い方はすぐわかる」ものばかりだ。しかし、今回紹介した `lastIndexOf()` のように、仕様の細かいニュアンス(インデックスの数え方、`fromIndex` の挙動、メモリ走査の方向)を深く理解していると、コードのパフォーマンスや安全性、そして何より「不測のバグを生みにくい設計」が自然とできるようになる。
「なんとなく動く」から「理由を持ってこれを選ぶ」へ。
日々のコードにそんなプロのこだわりを持っていこうぜ。さて、次のタスクに取りかかろうか。

コメント