JavaScript Tips | 文字列ユーティリティ:検索・置換 - 前方一致

JavaScript JavaScript
スポンサーリンク

「前方一致」とは何をしているのか

まず言葉の整理からいきます。
「前方一致」は、文字列の“先頭”が、指定した文字列と一致しているかどうかを調べることです。

「山田太郎」が「山田」で始まっているか?
「https://example.com」が「https://」で始まっているか?
「ERROR: DB connection failed」が「ERROR」で始まっているか?

こういう判定が全部「前方一致」です。
部分一致が「どこかに含まれていればOK」なのに対して、
前方一致は「先頭からきっちり合っているか」を見る、もう少し厳しめのチェックです。


JavaScript 標準の startsWith の基本

JavaScript には、前方一致専用のメソッド startsWith が用意されています。

const str = "Hello World";

str.startsWith("He");   // true
str.startsWith("Hello"); // true
str.startsWith("World"); // false(先頭ではない)
JavaScript

startsWith は、
「この文字列は、指定した文字列で“始まっているか”?」を true / false で返します。

第二引数で「どこから判定を始めるか」も指定できます。

const str = "Saturday night plans";

str.startsWith("Sat");      // true(先頭から)
str.startsWith("Sat", 3);   // false(インデックス3から見ると "urday...")
JavaScript

ここでの重要ポイントは二つです。

1つ目は、「前方一致=startsWith が素直に使える」ということ。
2つ目は、「大文字・小文字は区別される」ということです。

"Hello".startsWith("He"); // true
"Hello".startsWith("he"); // false(小文字なので一致しない)
JavaScript

業務で使いやすくする:大文字・小文字を無視した前方一致

実務では、「大文字・小文字の違いは気にしたくない」ことが多いです。
例えば、URL のプロトコル判定や、コマンド名の判定などです。

そこで、「大文字・小文字を無視した前方一致」ユーティリティを一つ用意しておきます。

function startsWithIgnoreCase(text, prefix) {
  if (text == null || prefix == null) return false;

  const t = String(text).toLowerCase();
  const p = String(prefix).toLowerCase();

  if (p === "") return true; // 空文字は「常に先頭一致」とみなす

  return t.startsWith(p);
}
JavaScript

やっていることを噛み砕きます。

text / prefix が null や undefined でも落ちないようにしている。
両方を文字列化してから toLowerCase() で小文字にそろえる。
プレフィックスが空文字なら true(検索 UI 的に自然な挙動)。
最終的には startsWith で前方一致を判定する。

使い方はこんな感じです。

startsWithIgnoreCase("HelloWorld", "he");   // true
startsWithIgnoreCase("helloWorld", "HE");   // true
startsWithIgnoreCase("WorldHello", "he");   // false
JavaScript

これだけで、「大文字・小文字を気にしない前方一致」がどこでも同じ挙動で使えるようになります。


さらに日本語環境らしく:全角・半角も吸収した前方一致

日本語の業務システムだと、「全角・半角の揺れ」もよく問題になります。

「HTTP://example.com」でも「http://」として扱いたい。
「 ERROR: …」(全角スペース)でも「error」で始まるとみなしたい。

そこで、「検索用の正規化」を一箇所にまとめてから startsWith する、という形にします。

まずは簡易的な全角→半角+小文字化の正規化関数です。

function toHalfWidth(str) {
  if (str == null) return "";
  return String(str)
    .replace(/[!-~]/g, (ch) =>
      String.fromCharCode(ch.charCodeAt(0) - 0xFEE0)
    )
    .replace(/ /g, " ");
}

function normalizeForPrefix(str) {
  return toHalfWidth(str).toLowerCase().trimStart();
}
JavaScript

ここでは、

全角英数字・記号を半角に寄せる。
全角スペースを半角スペースにする。
小文字にそろえる。
先頭の空白だけ削る(前方一致なので先頭が大事)。

という正規化をしています。

これを使って、「ゆるい前方一致」を作ります。

function startsWithLoose(text, prefix) {
  if (text == null || prefix == null) return false;

  const t = normalizeForPrefix(text);
  const p = normalizeForPrefix(prefix);

  if (p === "") return true;

  return t.startsWith(p);
}
JavaScript

動きを見てみます。

startsWithLoose("HTTP://example.com", "http://");   // true
startsWithLoose("  http://example.com", "HTTP://");     // true(先頭スペース無視+大文字小文字無視)
startsWithLoose(" ERROR: something", "error");   // true(全角スペース+全角英字)
startsWithLoose("INFO: ok", "error");                   // false
JavaScript

ここでの一番大事なポイントは、

前方一致の前に“どう正規化するか”をユーティリティに閉じ込めている」ことです。
呼び出し側は startsWithLoose を呼ぶだけで、
大文字・小文字・全角・半角・先頭スペースを意識せずに済みます。


実務での具体的な使いどころ

URL のプロトコル判定

「http か https かで処理を分けたい」というのは、よくあるパターンです。

function isHttpUrl(url) {
  return startsWithIgnoreCase(url, "http://");
}

function isHttpsUrl(url) {
  return startsWithIgnoreCase(url, "https://");
}

isHttpUrl("HTTP://example.com");  // true
isHttpsUrl("https://example.com"); // true
JavaScript

startsWithIgnoreCase を使うことで、
大文字・小文字を気にせずに素直に書けます。

ログレベルやメッセージ種別の判定

ログの先頭に「ERROR」「WARN」「INFO」などを付けている場合、
前方一致でレベルを判定できます。

function isErrorLog(message) {
  return startsWithLoose(message, "error");
}

isErrorLog("ERROR: DB connection failed");   // true
isErrorLog("error: timeout");                // true
isErrorLog(" INFO: ok");                     // false(先頭が info)
JavaScript

「先頭に何が書いてあるか」で分岐する処理は、
前方一致と相性がとても良いです。

入力補完・サジェストのフィルタ

検索ボックスやプルダウンの候補を、
「先頭がキーワードと一致するものだけ」に絞りたいこともあります。

const items = ["Apple", "Apricot", "Banana", "Grape"];

function filterByPrefix(list, keyword) {
  return list.filter((item) => startsWithIgnoreCase(item, keyword));
}

filterByPrefix(items, "ap"); // ["Apple", "Apricot"]
JavaScript

部分一致だとヒットしすぎるときに、
前方一致で「頭から合っているものだけ」に絞ると、
ユーザーにとっても分かりやすい挙動になります。


設計として意識してほしいこと

「前方一致のルール」をユーティリティに閉じ込める

一番大事なのは、
「前方一致のとき、何を無視して、何を区別するか」を決めて、
それをユーティリティに閉じ込めることです。

大文字・小文字を無視するのか。
全角・半角をそろえるのか。
先頭の空白を無視するのか。

これを画面ごと・機能ごとにバラバラに書き始めると、
「この画面ではヒットするのに、あっちではヒットしない」といった
気持ち悪い差がどんどん増えていきます。

なので、

normalizeForPrefix のような「前方一致用の正規化関数」を一つ決める。
前方一致をしたいときは、必ず startsWithIgnoreCasestartsWithLoose を通す。

というルールにしておくと、
検索・判定まわりの挙動が一貫して、テストもしやすくなります。


ちょっとだけ手を動かしてみる

コンソールで、次のあたりを試してみてください。

"HelloWorld".startsWith("He");
"HelloWorld".startsWith("he");

startsWithIgnoreCase("HelloWorld", "he");
startsWithLoose("HTTP://example.com", "http://");
startsWithLoose("  ERROR: something", "error");
JavaScript

「どこからが true で、どこからが false になるか」を体で覚えると、
「前方一致」と「正規化」のイメージがかなりクリアになります。

そのうえで、自分のプロジェクトに

export function startsWithIgnoreCase(text, prefix) { ... }
export function startsWithLoose(text, prefix) { ... }
JavaScript

のようなユーティリティを一つ置いて、
「前方一致判定は必ずここを通す」というルールにしてみてください。

それができた瞬間、あなたの検索・判定ロジックは
「その場しのぎの startsWith の寄せ集め」から
「設計された前方一致ユーティリティ」に、一段レベルアップします。

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