おい、調子はどうだい?
最近、コードレビューをしていて「あぁ、ここでその書き方しちゃうか……」と頭を抱えることが増えてきたんだ。特に文字列置換のあたりね。
「文字を置き換えたいから、とりあえず `replace()` 使っとけばいいか」って感じで、第一引数に普通の文字列をポイッと渡して、気づいたら「最初の1箇所しか変わってねぇじゃん!」っていうバグを仕込んじまう若手が後を絶たない。
今日はね、JavaScriptの `String.prototype.replace()`、その中でも「第一引数に文字列を渡した場合の挙動」に的を絞って、裏側の仕様から現場で使える実践テクニックまで、徹底的に叩き込んでやろうと思う。
公式ドキュメントには書いてあるようで書いていない、プロの現場のリアルな知見を共有するから、しっかりついてきな。
—
なぜ中級者ほど `replace()` の「文字列指定」でハマるのか?
さて、フロントエンドの開発現場でユーザーの入力をサニタイズしたり、APIから返ってきたメッセージのフォーマットを整えたりするとき、文字列置換は毎日のように使う相棒だ。
ここで問題だ。君がもし、次のようなコードを書いたとしたら、出力はどうなると思う?
const message = “apple banana apple cherry”;
const result = message.replace(“apple”, “orange”);
console.log(result);
// 出力は何になる?
「そりゃあ全部 `apple` が `orange` に変わって `orange banana orange cherry` になるでしょ?」と思ったそこの君。残念、大不正解だ。
正解は、こうなる。
“orange banana apple cherry”
そう、最初に見つかった1箇所しか置換されない。
これが、JavaScriptの `replace()` の第一引数に「文字列」を渡したときの仕様の罠なんだよ。
ブラウザの裏側で何が起きているのか?
この挙動、実はJavaScriptの仕様(ECMAScript)できっちり決められている。
`replace(searchValue, replaceValue)` に第一引数として「文字列」が渡された場合、内部的には以下の処理が走っている。
1. `searchValue` を正規表現オブジェクトに変換するわけではない。
2. 文字列としての完全一致を、左から右へ(先頭からスキャンして)検索する。
3. 最初に見つかった(マッチした)部分だけを新しい文字列に置き換える。
4. そこで検索を打ち切り、残りの文字列はそのまま返す。
つまり、第一引数に文字列を指定した場合、`replace()` は本質的に「最初の1つ目をピンポイントで置き換えるメソッド」として動作するんだ。全部を置き換えたければ、本来はグローバル検索フラグ(`/g`)を持った正規表現を渡す必要がある。ここを勘違いして「なんで全部置換されないんだ!」とキレるエンジニアを、俺は今まで何人も見てきた。
—
現場で即戦力になる!具体的なユースケースとコード例
じゃあ、この「第一引数に文字列を渡す仕様」は、実務のどんな場面で武器になるのか?
「最初の1箇所だけを変えたい」というシチュエーションは、フロントエンドでは意外と多い。
いくつか、現場でそのままコピペして使える綺麗なサンプルコードを見せよう。
ケース1: URLのプロトコル部分や特定のプレフィックスの厳密な置換
例えば、ユーザーが入力したパスやURLの先頭にある特定のスラッシュや文字列を、安全に1回だけ除去したいときだ。正規表現の特殊文字(`.` や “ など)をエスケープする面倒くささから解放されるため、第一引数に「生きた文字列」を渡すアプローチが非常に安全に機能する。
/
- ファイルパスの先頭にある不要なスラッシュを1つだけ安全に取り除く関数
- @param {string} path – 処理対象のパス
- @returns {string} – 整形されたパス
/
function sanitizeLeadingSlash(path) {
// 第一引数に文字列 “/l” を渡すことで、先頭付近の最初のマッチだけをピンポイントで置換
// ここで正規表現を使うとエスケープ漏れのバグを生みやすいが、文字列なら安全
const target = “/”;
if (path.startsWith(target)) {
// 最初の1文字目だけを空文字に置換する
return path.replace(target, “”);
}
return path;
}
console.log(sanitizeLeadingSlash(“/images/avatar.png”)); // 出力: “images/avatar.png”
console.log(sanitizeLeadingSlash(“images/avatar.png”)); // 出力: “images/avatar.png”(安全にスルー)
ケース2: テンプレート文字列や定型文のピンポイントなプレースホルダー置換
バックエンドから「`{username}` さん、こんにちは!」というテンプレート文字列が降ってきたとする。これを最初の1回だけ自分の名前に書き換えたい。
/
- 挨拶文のプレースホルダーを最初の1箇所だけ置換する
- @param {string} template – テンプレート文字列
- @param {string} name – 挿入するユーザー名
- @returns {string}
/
function applyUsername(template, name) {
// テンプレート内の最初の “{name}” だけをユーザー名に置き換える
// 正規表現を書く必要すらないため、可読性が圧倒的に高い
return template.replace(“{name}”, name);
}
const templateStr = “こんにちは、{name}さん! 本日のラッキーアイテムは{name}ではありませんが……”;
const result = applyUsername(templateStr, “Aki”);
console.log(result);
// 出力: “こんにちは、Akiさん! 本日のラッキーアイテムは{name}ではありませんが……”
// 解説: 2回目の “{name}” はそのまま残る。これが文字列指定の正しいユースケースだ。
—
⚠️ シニアからの警告:モダンJSにおける「全置換」の落とし穴
「いや、俺は文字列を指定して、どうしても全部の文字列を置換したいんだ!」という頑固なやつもいるだろう。
従来の書き方だと、全部置換するにはこう書くしかなかった。
// 古臭い、またはエスケープの手間がかかるやり方
const text = “foo-bar-foo-baz-foo”;
// 文字列を直接グローバル置換することは replace の仕様上できないため、
// 正規表現のコンストラクタに突っ込む必要があるが、特殊文字のエスケープ地獄が待っている
もし君がNode.jsの最新環境や、モダンなブラウザターゲットで開発しているなら、朗報がある。
ECMAScript 2021で導入された `String.prototype.replaceAll()` を使うべきだ。
const text = “apple banana apple cherry apple”;
// 第一引数に「文字列」を渡しても、マッチしたすべての箇所を置換してくれる!
const result = text.replaceAll(“apple”, “orange”);
console.log(result);
// 出力: “orange banana orange cherry orange”
もしターゲットブラウザの兼ね合いで `replaceAll` が使えない古のプロジェクトだったとしても、無理やり文字列のまま `replace()` を使おうとしてハマるな。その場合は素直に正規表現とグローバルフラグ(`/g`)を使うか、`split().join()` のテクニックを使うことだ。
—
まとめ:プロとしての心構え
JavaScriptのメソッドは、一見するとどれも同じように動くように見えて、実は引数の型(StringなのかRegExpなのか)によって裏側の挙動がガラリと変わる。
- `replace()` の第一引数に「文字列」を渡した場合: 最初の1箇所しか置換されない(これは仕様であり、バグではない)。
- 最初の1箇所だけを安全に変えたい場合: 特殊文字のエスケープを気にせず使える「文字列指定の `replace()`」が最強の選択肢になる。
- 全部変えたい場合: `replaceAll()` を使うか、適切な正規表現を設計する。
仕様を「なんとなく」でコードを書くのと、「裏側でどう処理されているか」を理解してコードを書くのとでは、1年後、3年後に書くコードの品質に天と地ほどの差が出る。
さあ、理屈は分かったはずだ。
自分のプロジェクトに戻って、`replace` を適当に使っている箇所がないか、今すぐコードレビューしに行こうぜ。頼んだぞ!

コメント