ウェブエンジニア問題集

文字列操作と正規表現 — replace・split・slice・正規表現の実践ガイド

フォームの入力値を整える、URLを組み立てる、APIから来たテキストを加工する——文字列操作は配列と並んで毎日触る処理です。

この章では、文字列の「メソッド」とは何かという前提から始めて、sliceincludesreplace/replaceAllsplit など実務で頻出するメソッドと、テンプレートリテラル・正規表現の基本を用途別に整理します。

学習者学習者

文字列のメソッドって数が多くて、毎回MDNを開いて調べてる…。よく使うものだけ整理してほしい。

この章は、その「よく使うもの」を用途別にまとめた早見表のように使えます。前章の配列メソッド完全ガイドと対になる構成です。

そもそも文字列メソッドとは — String.prototype のメソッド

'hello'.toUpperCase() のように、文字列リテラルに対して直接メソッドを呼べるのはなぜでしょうか。文字列は本来オブジェクトではなくプリミティブ値なのに、です。

const s = 'hello';
console.log(typeof s); // "string"(オブジェクトではない)
s.toUpperCase(); // なのにメソッドが呼べる

これは、メソッドを呼んだ瞬間にJavaScriptが文字列を一時的に String オブジェクトに包む(自動ボックス化) からです。メソッドは String.prototype に定義されており、そこから借りて実行されます。配列が Array.prototype からメソッドを借りるのと同じ仕組みです。

console.log(String.prototype.hasOwnProperty('toUpperCase')); // true
先生先生

だから「文字列メソッド」=「String.prototype のメソッド」。配列のときと同じ考え方だね。

文字列を加工して新しい文字列を作るイメージ

文字列はイミュータブル — すべてのメソッドは「新しい文字列」を返す

最重要の前提です。文字列は一度作ると中身を変更できません(イミュータブル)。 そのため、replacetoUpperCase などのメソッドは元の文字列を変えず、加工した新しい文字列を返します

const original = 'hello';
const upper = original.toUpperCase();
 
console.log(upper); // "HELLO"(新しい文字列)
console.log(original); // "hello"(元はそのまま)
学習者学習者

text.replace(...) を呼んだのに元の変数が変わってない…!バグかと思った。

バグではありません。戻り値を受け取らないと結果は捨てられます。const result = text.replace(...) のように必ず戻り値を使うのが鉄則です。

テンプレートリテラル — 変数を埋め込む

バッククォート(`)で囲むと、${...} の中に変数や式を埋め込めます。文字列の + 連結より圧倒的に読みやすく、現在の標準です。

const name = '太郎';
const age = 25;
 
// ❌ + 連結は読みにくい
const a = 'こんにちは、' + name + 'さん(' + age + '歳)';
 
// ⭕ テンプレートリテラル
const b = `こんにちは、${name}さん(${age}歳)`;
 
// 改行もそのまま書ける
const html = `
  <p>${name}</p>
  <p>${age}</p>
`;

抽出 — slice / substring / at

文字列の一部を切り出します。実務では slice を覚えておけばほぼ事足ります

構文: str.slice(start, end?)

引数説明
start切り出しを始める位置(0始まり)。マイナスを指定すると末尾から数える
end(省略可)切り出しを終える位置。その手前までが対象で end 自身は含まない。省略すると末尾まで

戻り値: 切り出した新しい文字列(元の文字列は変わらない)

at は1文字を取り出すメソッドで、構文は str.at(index)index にマイナスを指定すると末尾から数えられます(-1 で最後の文字)。

const str = 'JavaScript';
 
str.slice(0, 4); // "Java"(0番目から4番目の手前まで)
str.slice(4); // "Script"(4番目から最後まで)
str.slice(-6); // "Script"(末尾から6文字。マイナス指定が便利)
 
str.at(0); // "J"(最初の文字)
str.at(-1); // "t"(最後の文字。str[str.length - 1] より簡潔)

検索 — includes / indexOf / startsWith / endsWith

文字列が含まれているか・どこにあるかを調べます。いずれも「探したい文字列」を引数に渡すだけです。

メソッド引数戻り値
str.includes(searchString)searchString:探す文字列boolean(含まれるか)
str.indexOf(searchString)searchString:探す文字列最初に現れた位置(数値)。見つからなければ -1
str.startsWith(searchString)searchString:探す文字列boolean(前方一致するか)
str.endsWith(searchString)searchString:探す文字列boolean(後方一致するか)
const url = 'https://example.com/users/1';
 
url.includes('users'); // true(含まれるか → boolean)
url.startsWith('https'); // true(前方一致)
url.endsWith('.com'); // false
url.indexOf('users'); // 20(位置。見つからなければ -1)

置換 — replace / replaceAll

文字列を置き換えます。実務で非常によく使う一方、ハマりやすい挙動があるので丁寧に見ます。

構文: str.replace(pattern, replacement)

引数渡せるもの説明
pattern(第1引数)文字列 / 正規表現何を置き換えるか。文字列を渡すと最初にマッチした1件だけが対象になる(要注意)
replacement(第2引数)文字列 / 関数何に置き換えるか。文字列の中で $1$2… で正規表現のキャプチャ(後述)を参照でき、関数を渡すとマッチした値から動的に計算できる

戻り値: 置換後の新しい文字列(元の文字列は変わらない)

replaceAll(pattern, replacement) も引数の意味は同じで、違いはマッチしたすべてを置き換える点だけです。

この2つの引数(patternreplacement)にそれぞれ何を渡せるかで挙動が変わります。パターン別に見ていきましょう。

基本(文字列で指定すると「最初の1件」だけ)

const text = 'りんご, りんご, みかん';
 
text.replace('りんご', 'ぶどう');
// "ぶどう, りんご, みかん" ← 最初の1件だけ置換される!
学習者学習者

全部置き換えたいのに、1個目しか変わらない…!なんで?

replace文字列を渡すと、置換されるのは最初にマッチした1件だけです。これは初心者がほぼ必ず引っかかるポイントです。すべて置換するには次の2つの方法があります。

すべて置換する — replaceAll(推奨)

const text = 'りんご, りんご, みかん';
 
text.replaceAll('りんご', 'ぶどう');
// "ぶどう, ぶどう, みかん" ← 全部置換される

replaceAll(ES2021)が最もシンプルで読みやすい方法です。古い環境向けには、後述の正規表現に g(グローバル)フラグを付ける方法もあります。

// 正規表現 + g フラグでも全置換できる(replaceAll が使えない環境向け)
text.replace(/りんご/g, 'ぶどう');

正規表現で柔軟に置換 + キャプチャ($1)

第1引数に正規表現を渡すと、パターンマッチで置換できます。() でグループ化した部分は、置換文字列の中で $1$2… として参照できます。

// 2024-01-31 → 2024/01/31 に変換
'2024-01-31'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$1/$2/$3');
// "2024/01/31"

コールバックで動的に置換

置換後の値を関数で計算することもできます。マッチした文字列が引数で渡されます。

// 各単語の先頭を大文字に
'hello world'.replace(/\b\w/g, (char) => char.toUpperCase());
// "Hello World"

大文字小文字・空白の処理

'Hello'.toUpperCase(); // "HELLO"
'Hello'.toLowerCase(); // "hello"
 
'  hello  '.trim(); // "hello"(前後の空白を除去)
'  hello  '.trimStart(); // "hello  "
'5'.padStart(3, '0'); // "005"(左を0埋め。ゼロパディングの定番)
'5'.padEnd(3, '*'); // "5**"

padStart / padEnd の構文は str.padStart(targetLength, padString?) です。

引数説明
targetLength最終的に揃えたい文字数。元の文字列がすでにこの長さ以上なら何も埋めない
padString(省略可)埋めるのに使う文字。デフォルトは半角スペース

分割と結合 — split / join

文字列を配列に分割(split)し、配列を文字列に結合(join)します。配列処理との橋渡しになる重要メソッドです。

メソッド引数戻り値
str.split(separator, limit?)separator:区切り文字('' を渡すと1文字ずつ) / limit(省略可):取り出す最大要素数配列
arr.join(separator?)separator(省略可):要素をつなぐ文字(省略すると ,文字列
// 分割:文字列 → 配列
'a,b,c'.split(','); // ["a", "b", "c"]
'hello'.split(''); // ["h", "e", "l", "l", "o"](1文字ずつ)
 
// 結合:配列 → 文字列
['a', 'b', 'c'].join('-'); // "a-b-c"
 
// 組み合わせ:CSVの各値をトリムする
'  a , b , c '
  .split(',')
  .map((s) => s.trim())
  .join(','); // "a,b,c"

正規表現の基本

正規表現(RegExp)は「文字列のパターン」を表す仕組みです。replace や検索と組み合わせて使います。組み合わせるメソッドの引数は次のとおりです。

メソッド引数戻り値
regex.test(str)str:調べる文字列boolean(マッチするか)
str.match(regex)regex:正規表現マッチ情報。g なしなら最初のマッチ+キャプチャ、なければ null
str.matchAll(regex)regex:正規表現(g フラグ必須)すべてのマッチのイテレータ
const re = /\d{3}-\d{4}/; // 「数字3桁-数字4桁」のパターン(郵便番号など)
 
re.test('123-4567'); // true(マッチするか → boolean)
'郵便番号は123-4567です'.match(re); // マッチ部分の情報を返す
 
// すべてのマッチを取り出す(matchAll + g フラグ)
const text = 'tel: 03-1234, 06-5678';
[...text.matchAll(/\d{2}-\d{4}/g)].map((m) => m[0]);
// ["03-1234", "06-5678"]

よく使うパターン部品:

記法意味
\d数字1文字(\D は数字以外)
\w英数字・アンダースコア
\s空白文字
.任意の1文字
+直前を1回以上
*直前を0回以上
?直前を0回または1回
{n}直前をn回
^ / $行頭 / 行末
[abc]a,b,cのいずれか
``

主なフラグ:g(全マッチ)、i(大文字小文字を無視)、m(複数行)。

メソッド早見表

やりたいことメソッド戻り値
一部を切り出すslice新しい文字列
含まれるか調べるincludesboolean
前方/後方一致startsWith / endsWithboolean
最初の1件を置換replace新しい文字列
すべて置換replaceAll新しい文字列
大文字/小文字化toUpperCase / toLowerCase新しい文字列
前後の空白除去trim新しい文字列
区切って配列にsplit配列
パターンに一致するか正規表現 + testboolean

よくあるハマりどころ

文字列操作のハマりどころを確認するイメージ

1. replace が最初の1件しか置換しない

前述のとおり、文字列指定の replace は最初の1件だけ。全置換は replaceAll(または /.../g)を使います。

2. 戻り値を使い忘れる

文字列はイミュータブル。text.trim() だけでは何も起きません。text = text.trim() のように戻り値を代入します。

3. length と「見た目の文字数」がずれる

絵文字や一部の文字は、length 上は2以上としてカウントされます。

'👨‍👩‍👧'.length; // 8(見た目は1文字なのに)

正確な「文字単位」で数えたい場合は [...str].length(スプレッドで分割)や Intl.Segmenter を使います。

ちゃんと使うためのポイント

  • 文字列メソッドは String.prototype のメソッド。文字列はイミュータブルで、メソッドは新しい文字列を返す(戻り値を必ず使う)
  • 連結はテンプレートリテラル(`${x}`)、抽出は slice、含有判定は includes が基本
  • 全置換は replaceAllreplace に文字列を渡すと最初の1件だけなので注意
  • 入力値は trim() で前後空白を落としてから扱う
  • 複雑なパターンの検索・置換は正規表現と組み合わせる

次の章では、分割代入とスプレッド構文 を扱います。split で作った配列の受け取りなど、ここで学んだ文字列操作とも自然につながります。