こんにちは! 現場のコードベースと日々格闘しているフロントエンドエンジニアの皆さん、調子はどうでしょうか。
フロントエンドのコードを書いていると、避けて通れないのが「文字列操作」です。特にユーザーの入力値のバリデーションや、APIから返ってきたごちゃ混ぜのテキストデータから特定のパターンを検出する処理は、日常茶飯事ですよね。
「文字列の中から特定の文字を探す」となったとき、あなたは何を使いますか?
おなじみの `indexOf()` でしょうか? それともES6以降のモダンな `includes()`? あるいは、正規表現でガッツリ抽出するために `match()` や `matchAll()` を持ち出しますか?
実は、これらの陰に隠れがちですが、「正規表現を使って、マッチした最初の位置(インデックス)を知りたい」 という局所的な要件において、抜群の切れ味を誇るメソッドがあります。それが今回フォーカスする `String.prototype.search()` です。
「知ってはいるけど、あまり使ったことがない」「`indexOf` や `match` と何が違うの?」と思っている中堅のあなたに向けて、仕様の裏側から現場で使える泥臭い Tips、そしてパフォーマンスの真実までを徹底的に解説します。これを読めば、明日からのコードレビューでドヤ顔ができること間違いなしです。
—
1. `String.prototype.search()` の基本仕様と「裏側の挙動」
まずは基本に立ち返り、仕様の骨子を整理しましょう。
`search()` メソッドは、与えられた正規表現にマッチする箇所が文字列内にあるかどうかを調べ、最初に見つかった位置のインデックス(0始まり)を返します。
最大の特徴であり、絶対に忘れてはならないポイントは以下の2点です。
1. 見つからなかった場合は `-1` を返す(`indexOf` と同じ挙動ですね)。
2. 引数は、内部的に必ず「正規表現(RegExp)」に変換される。
引数の「暗黙の変換」という罠
ここが最初の泥臭いポイントです。もし `search()` の引数に正規表現オブジェクト(`/pattern/`)ではなく、普通の文字列(`”pattern”`)を渡すとどうなるでしょうか?
const text = “Hello, world!”;
// 文字列を渡してみる
console.log(text.search(“world”)); // 7
一見、問題なく動いているように見えます。しかし、JavaScript(V8などのブラウザエンジン)の裏側では、`new RegExp(“world”)` が暗黙的に実行され、強制的に正規表現オブジェクトに変換されてから処理されています。
これは、単純な部分一致を調べたいだけであれば、無駄なオーバーヘッド(正規表現コンパイル)が発生していることを意味します。単一の固定文字列を探したいだけなら、絶対に `indexOf()` を使うべきです。`search()` を使うのは、「正規表現のパワーが必要なときだけ」 と心に刻んでおきましょう。
—
2. ブラウザのエンジン(V8など)は裏側でどう動いているか?
JavaScriptの仕様(ECMAScript)をめくってみると、`String.prototype.search` の挙動は非常に興味深い設計になっています。
実は、`search(regexp)` を呼び出したとき、エンジンは単に文字列を走査しているわけではありません。内部的には、引数のオブジェクトが持つ `Symbol.search` メソッドを呼び出しています。
つまり、内部処理的には以下のような流れになります。
1. `search(query)` が呼ばれる。
2. `query` が `RegExp` でない場合、`new RegExp(query)` で正規表現に変換される。
3. `query[Symbol.search](this)` が実行される。
`g`(グローバル)フラグは完全に無視される仕様
ここで、多くのエンジニアが一度はハマる「仕様の罠」があります。
正規表現に `g`(グローバル検索)フラグを付けたら、2番目、3番目のマッチ位置も取れるのでは? と期待してしまいますよね。
const text = “cat, bat, rat”;
// グローバルフラグを付けて検索してみるが…
console.log(text.search(/[cbr]at/g)); // 0 (最初の ‘cat’ の位置だけ)
結論から言うと、`g` フラグは完全にシカトされます。
なぜかというと、`search()` の仕様上、マッチングを開始する前に正規表現オブジェクトの `lastIndex`(次にマッチングを開始する位置のポインタ)が強制的に `0` にリセットされるからです。そして、最初の一致が見つかった時点で処理は終了し、インデックスを返します。
「複数箇所の一致を追いたい」場合は、`search()` ではなく、`matchAll()` や `RegExp.prototype.exec()` をループで回すのが正解です。用途の切り分けをしっかり行いましょう。
—
3. 実務で使える極上のサンプルコード
それでは、実際の現場で遭遇するユースケースを想定した、そのままコピペして使える綺麗なサンプルコードを紹介します。
今回は「ユーザーが入力した電話番号のフォーマットチェック」や「大文字小文字を区別しない柔軟なプレフィックス判定」をテーマに、実戦的なコードを書いてみました。
/
- ユーザーの入力文字列から、特定のパターン(日本の電話番号など)が
- 開始されているインデックスを特定し、有効なデータの開始位置を返します。
- @param {string} input – 検査対象の文字列
- @returns {number} パターンが見つかったインデックス(見つからない場合は -1)
/
function findPhoneNumberStartIndex(input) {
// 日本の電話番号パターン(携帯・固定電話の簡易判定)
// 0 から始まり、2〜4桁の市外局番 – 2〜4桁 – 3〜4桁
const phonePattern = /0\d{1,4}-\d{1,4}-\d{3,4}/;
// searchメソッドを実行。内部で正規表現オブジェクトが評価されます。
const index = input.search(phonePattern);
// 見つからなかった場合の -1 判定は、マジックナンバーを避け、
// 明示的に比較することが可読性を上げるコツです。
if (index === -1) {
console.warn(“電話番号のパターンが見つかりませんでした。”);
return -1;
}
return index;
}
// — 動作検証 —
const log1 = “ログのヘッダー情報: 090-1234-5678 からの着信を受信しました。”;
const index1 = findPhoneNumberStartIndex(log1);
console.log(`開始位置: ${index1}`); // 開始位置: 11 (090の ‘0’ の位置)
const log2 = “エラーログ: 不正なアクセスを検知しました。”;
const index2 = findPhoneNumberStartIndex(log2);
console.log(`開始位置: ${index2}`); // 開始位置: -1 (見つからない)
/
- 特定のキーワード(大文字小文字を区別しない)が
- 文字列の「先頭」にあるか、あるいは「どこか」に含まれているかを判定する
/
function analyzeKeywordPosition(text) {
// ‘error’ または ‘critical’ という文字を、大文字小文字を区別せず検索 (i フラグ)
const pattern = /error|critical/i;
const position = text.search(pattern);
if (position === 0) {
return “🚨 即時対応:文頭に重大なキーワードを検出しました!”;
} else if (position > 0) {
return `⚠️ 要確認:位置 ${position} にキーワードを検出しました。`;
} else {
return “✅ 安全:問題となるキーワードは見つかりませんでした。”;
}
}
console.log(analyzeKeywordPosition(“ERROR: Database connection failed.”));
// -> 🚨 即時対応:文頭に重大なキーワードを検出しました!
console.log(analyzeKeywordPosition(“[System Log] a critical issue occurred.”));
// -> ⚠️ 要確認:位置 14 にキーワードを検出しました。
—
4. 現場でハマりがちな落とし穴とベストプラクティス
最後に、シニアエンジニアとしてチームのコードレビューでよく指摘する、`search()` にまつわる「アンチパターン」と「ベストプラクティス」を共有します。
アンチパターン:「含まれているかどうか(真偽値)」を知りたいだけなのに `search() !== -1` を使う
実はこれが一番多い罠です。
// ❌ もったいない書き方
if (text.search(/admin/i) !== -1) {
// 管理者向け処理
}
このコード、動くには動きます。しかし、やりたいことが「真偽値(boolean)の取得」だけであれば、`search()` を使うのはパフォーマンス的

コメント