【実務・中級編】 String.prototype.startsWith() – JavaScript実践ガイド

お疲れ。今日のコードレビューで、また「あの書き方」を見かけたんだよね。

// レビューでよく見るやつ
if (str.indexOf(‘prefix_’) === 0) {
// 処理
}

おいおい、平成か!……いや、冗談はさておき、中級からもう一歩抜け出して「読める・メンテナブルなコード」を書くフェーズにいる君たちなら、このコードの何がもったいないかピンとくるはずだ。そう、意図が直感的に伝わらないし、何よりマジックナンバー(`0`)がポツンと置いてあって美しくない。

今回は、文字列が特定のパターンで始まるかをスマートかつ高速に判定する `String.prototype.startsWith()` について、実務で絶対に知っておくべき仕様の裏側と、現場で使える実践テクニックを叩き込んでいく。

—

1. なぜ今さら `startsWith()` なのか?(基本仕様のおさらい)

まずは基本だ。`startsWith()` は、ある文字列が特定の文字列で始まるかどうかを真偽値(`true` / `false`)で返す。

const trackingId = ‘JP_987654321’;

// 基本的な使い方
console.log(trackingId.startsWith(‘JP’)); // true
console.log(trackingId.startsWith(‘US’)); // false

これだけなら「ああ、正規表現の `^` や `indexOf()0` の糖衣構文(シンタックスシュガー)ね」で終わるかもしれない。だが、フロントエンドの現場でこのメソッドが重宝される理由は、その「柔軟な第2引数」にある。

知られざる第2引数:検索開始位置(`position`)

意外と見落とされがちなのが、第2引数に「どこから探し始めるか」のインデックスを指定できる点だ。

const path = ‘/admin/users/123’;

// ‘/admin’ から始まっているか?
console.log(path.startsWith(‘/admin’, 0)); // true

// インデックス6番目(usersの前)から ‘users’ で始まっているか?
console.log(path.startsWith(‘users’, 7)); // true

この「オフセットを指定できる」という仕様、実はパーサーの自作や、複雑なルーティングの文字列解析、巨大なCSVやログ行のパース処理などでめちゃくちゃ効いてくる。無駄に `slice()` や `substring()` で文字列を切り出すコストを払わなくて済むわけだ。

—

2. ブラウザの裏側で何が起きているか?(V8エンジンの視点)

フロントエンドエンジニアとして一歩先に行くなら、「メソッドの裏側でJSエンジンがどう動いているか」を知っておく必要がある。

V8(ChromeやNode.jsのエンジン)などのモダンなJSエンジンにおいて、文字列は内部的にUTF-16(またはラテン1文字ならLatin-1)の連続したメモリ領域として保持されている。

`startsWith()` が実行されるとき、エンジンは以下のような最適化と処理を行っている。

1. 型の強制(Type Coercion): 引数に渡されたものが文字列でない場合、内部的に文字列へ変換される(例: `String(searchString)`)。
2. 境界値のバリデーション: 指定された `position` が負の値であれば `0` に補正され、元の文字列の長さを超えていれば即座に `false` を返す(無駄な走査をしない!)。
3. 高速なメモリ比較(Memcmp): 文字列の先頭から比較を行い、一致しない文字が見つかった瞬間に処理を打ち切る(短絡評価)。

昔ながらの `indexOf() === 0` とのパフォーマンス上の決定的な違いはない(どちらも内部的にはネイティブレベルの高速な文字比較を行っている)が、コードの意図が「検索(positionを探す)」ではなく「検証(これで始まっているか)」であることが一瞬で伝わるという点で、可読性の勝利なのだ。

—

3. 実務で使える!現場のユースケース&ベストプラクティス

ここからが本題だ。実際のモダンなフロントエンド開発で、どうこのメソッドを活かすべきか、いくつか具体的なパターンを見ていこう。

ユースケース A: ルーティングやURLの権限チェック

SPA(Single Page Application)のルーターやミドルウェアで、特定のプレフィックスを持つパスに対して一括でガードをかけたい時によく使う。

/

  • リクエストパスが管理者向けのエンドポイントかどうかを判定する
  • @param {string} pathname – 現在のURLパス
  • @returns {boolean}

/
function isAmdinRoute(pathname) {
// 複数のプレフィックスに対応する場合
const adminPrefixes = [‘/admin’, ‘/settings’, ‘/moderator’];

// some() と組み合わせることで、どれか一つにヒットするかを綺麗に判定
return adminPrefixes.some(prefix => pathname.startsWith(prefix));
}

// 活用例
console.log(isAmdinRoute(‘/admin/dashboard’)); // true
console.log(isAmdinRoute(‘/api/v1/users’)); // false

正規表現をわざわざ生成して `test()` するよりも、コードが直感的で、パフォーマンス的にも安全かつ高速に評価できる。

ユースケース B: フォーム入力値のプレフィックス補正(正規化)

ユーザーが入力したSNSのIDやURLから、重複するプレフィックスを取り除いたり、逆に強制的に付与したりする場面。

/

  • ユーザー名が ‘@’ で始まっていなければ付与する(Twitter/Xのハンドルネームなど)
  • @param {string} inputId
  • @returns {string}

/
function normalizeTwitterId(inputId) {
const trimmed = inputId.trim();

// すでに ‘@’ で始まっていなければ付加
if (!trimmed.startsWith(‘@’)) {
return `@${trimmed}`;
}

return trimmed;
}

console.log(normalizeTwitterId(‘ kaz_log ‘)); // “@kaz_log”
console.log(normalizeTwitterId(‘@kaz_log’)); // “@kaz_log”

—

4. 注意すべき「落とし穴」(罠と回避策)

シニアとして、後輩によくあるアンチパターンや注意点も伝えておこう。

① 大文字・小文字の区別(Case Sensitivity)

`startsWith()` は厳密に大文字・小文字を区別する。ユーザーの入力を扱う際、これにハマると「動かないんだけど!」というバグチケットが飛んでくる。

const role = ‘Admin_User’;

console.log(role.startsWith(‘admin’)); // false (大文字小文字が違う)

// 【対策】比較する前に両方を小文字(または大文字)に揃える
console.log(role.toLowerCase().startsWith(‘admin’)); // true

※ ただし、パフォーマンスがクリティカルな巨大ループ内で毎回 `toLowerCase()` を呼ぶのは避けること。必要なら事前にデータ側を正規化しておくべきだ。

② IE11 の亡霊(環境依存)

古い社内システムや組込みブラウザをサポートしなければならない案件で、たまに `TypeError: str.startsWith is not a function` というエラーを踏む奴がいる。
`startsWith()` は ES2015 (ES6) で導入されたモダンなAPIだ。もし対象ブラウザにIE11が含まれているなら(もう令和も後半だが、まだ生息している現場もある)、Babel等のトランスピラorポリフィル(`core-js` など)が確実に効いているかビルド設定を確認しよう。
自前で書くなら、以下のようなフォールバックも一応頭の片隅に置いておくとプロっぽい。

// プロトタイプのポリフィルのイメージ(簡易版)
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(search, position) {
position = position || 0;
return this.indexOf(search, position) === position;
};
}

—

まとめ

たかが `startsWith()`、されど `startsWith()`。
こういう言語仕様の細かいプリミティブなメソッドをどれだけ引き出しに持っているかで、コードの美しさ、そしてチーム全体の保守性は大きく変わってくる。

  • 「で始まるか」の判定には `indexOf === 0` ではなく `startsWith()` を使え!(意図が明確になる)
  • 第2引数の `position` を使えば、不要な文字列の切り出し(`slice`など)を省ける!
  • 大文字・小文字の揺れには `.toLowerCase()` を挟むか事前正規化を忘れるな!

明日のプルリクエストから、さっそく「ダサいコード」をこのスマートなメソッドで置き換えていってくれ。レビューするのが楽しみにしてるよ。

コメント

タイトルとURLをコピーしました