2022年7月13日

RegExpとStringのメソッド

この記事では、正規表現を扱う様々なメソッドを詳細に解説します。

str.match(regexp)

str.match(regexp)メソッドは、文字列str内でregexpに一致するものを検索します。

このメソッドには3つのモードがあります。

  1. regexpにフラグgがない場合、最初のマッチを、キャプチャグループとプロパティindex(マッチの位置)、input(入力文字列、strと同じ)を持つ配列として返します。

    let str = "I love JavaScript";
    
    let result = str.match(/Java(Script)/);
    
    alert( result[0] );     // JavaScript (full match)
    alert( result[1] );     // Script (first capturing group)
    alert( result.length ); // 2
    
    // Additional information:
    alert( result.index );  // 7 (match position)
    alert( result.input );  // I love JavaScript (source string)
  2. regexpにフラグgがある場合、すべてのマッチを文字列の配列として返し、キャプチャグループやその他の詳細は含まれません。

    let str = "I love JavaScript";
    
    let result = str.match(/Java(Script)/g);
    
    alert( result[0] ); // JavaScript
    alert( result.length ); // 1
  3. マッチがない場合、フラグgの有無にかかわらず、nullが返されます。

    これは重要なニュアンスです。マッチがない場合、空の配列ではなく、nullが返されます。これを忘れてしまうと、例えば以下のようなミスを犯しやすいです。

    let str = "I love JavaScript";
    
    let result = str.match(/HTML/);
    
    alert(result); // null
    alert(result.length); // Error: Cannot read property 'length' of null

    結果を配列にしたい場合は、次のように書くことができます。

    let result = str.match(regexp) || [];

str.matchAll(regexp)

最近の追加事項
これは、言語に最近追加されたものです。古いブラウザではpolyfillが必要になる場合があります。

str.matchAll(regexp)メソッドは、str.matchの「新しい、改良された」バージョンです。

これは主に、すべてのグループを含むすべてのマッチを検索するために使用されます。

matchとの違いは3つあります。

  1. 配列ではなく、マッチを含む反復可能オブジェクトを返します。Array.fromを使用して、通常の配列を作成できます。
  2. すべてのマッチは、キャプチャグループを持つ配列として返されます(フラグgのないstr.matchと同じ形式)。
  3. 結果がない場合は、nullではなく、空の反復可能オブジェクトを返します。

使用例

let str = '<h1>Hello, world!</h1>';
let regexp = /<(.*?)>/g;

let matchAll = str.matchAll(regexp);

alert(matchAll); // [object RegExp String Iterator], not array, but an iterable

matchAll = Array.from(matchAll); // array now

let firstMatch = matchAll[0];
alert( firstMatch[0] );  // <h1>
alert( firstMatch[1] );  // h1
alert( firstMatch.index );  // 0
alert( firstMatch.input );  // <h1>Hello, world!</h1>

for..ofを使用してmatchAllのマッチをループする場合、Array.fromは不要です。

str.split(regexp|substr, limit)

正規表現(または部分文字列)を区切り文字として使用して、文字列を分割します。

splitは、次のように文字列で使用できます。

alert('12-34-56'.split('-')) // array of ['12', '34', '56']

しかし、同じように正規表現で分割することもできます。

alert('12, 34, 56'.split(/,\s*/)) // array of ['12', '34', '56']

str.search(regexp)

str.search(regexp)メソッドは、最初のマッチの位置を返し、見つからない場合は-1を返します。

let str = "A drop of ink may make a million think";

alert( str.search( /ink/i ) ); // 10 (first match position)

重要な制限事項:searchは最初のマッチのみを見つけます。

それ以降のマッチの位置が必要な場合は、str.matchAll(regexp)ですべてのマッチを見つけるなど、他の方法を使用する必要があります。

str.replace(str|regexp, str|func)

これは、検索と置換のための汎用的なメソッドであり、最も便利なメソッドの1つです。検索と置換のための万能ナイフです。

正規表現を使用せずに、部分文字列を検索して置換するために使用できます。

// replace a dash by a colon
alert('12-34-56'.replace("-", ":")) // 12:34-56

ただし、落とし穴があります。

replaceの最初の引数が文字列の場合、最初のマッチのみが置換されます。

上記の例では、最初の"-"のみが":"に置き換えられています。

すべてのハイフンを見つけるには、文字列"-"ではなく、必須のgフラグが付いた正規表現/-/gを使用する必要があります。

// replace all dashes by a colon
alert( '12-34-56'.replace( /-/g, ":" ) )  // 12:34:56

2番目の引数は置換文字列です。 इसमें 特殊文字を使用できます。

記号 置換文字列での動作
$& マッチ全体を挿入します
$` マッチの前の文字列の一部を挿入します
$' マッチの後の文字列の一部を挿入します
$n nが1桁または2桁の数字の場合、n番目のキャプチャグループの内容を挿入します。詳細はキャプチャグループを参照してください。
$<name> 指定されたnameを持つ括弧の内容を挿入します。詳細はキャプチャグループを参照してください。
$$ 文字$を挿入します

例えば

let str = "John Smith";

// swap first and last name
alert(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John

「スマートな」置換が必要な場合は、2番目の引数を関数にすることができます。

これは各マッチに対して呼び出され、返された値が置換として挿入されます。

関数は、引数func(match, p1, p2, ..., pn, offset, input, groups)で呼び出されます。

  1. match - マッチ
  2. p1, p2, ..., pn - キャプチャグループの内容(存在する場合)
  3. offset - マッチの位置
  4. input - ソース文字列
  5. groups - 名前付きグループを持つオブジェクト

正規表現に括弧がない場合、引数は3つだけです:func(str, offset, input)

例えば、すべてのマッチを大文字にします。

let str = "html and css";

let result = str.replace(/html|css/gi, str => str.toUpperCase());

alert(result); // HTML and CSS

各マッチを文字列内の位置に置き換えます。

alert("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6

以下の例では括弧が2つあるため、置換関数は5つの引数で呼び出されます。最初は完全なマッチ、次に2つの括弧、そしてその後に(例では使用されていません)マッチの位置とソース文字列が続きます。

let str = "John Smith";

let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`);

alert(result); // Smith, John

グループが多い場合は、レストパラメータを使用してアクセスすると便利です。

let str = "John Smith";

let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`);

alert(result); // Smith, John

または、名前付きグループを使用している場合、それらを持つgroupsオブジェクトは常に最後にあるため、次のように取得できます。

let str = "John Smith";

let result = str.replace(/(?<name>\w+) (?<surname>\w+)/, (...match) => {
  let groups = match.pop();

  return `${groups.surname}, ${groups.name}`;
});

alert(result); // Smith, John

関数を使用すると、マッチに関するすべての情報が取得され、外部変数にアクセスでき、すべてを実行できるため、究極の置換能力が得られます。

str.replaceAll(str|regexp, str|func)

このメソッドは基本的にstr.replaceと同じですが、2つの大きな違いがあります。

  1. 最初の引数が文字列の場合、文字列の*すべて*の出現箇所を置換しますが、replaceは*最初*の出現箇所のみを置換します。
  2. 最初の引数がgフラグのない正規表現の場合、エラーが発生します。gフラグを付けると、replaceと同じように動作します。

replaceAllの主なユースケースは、文字列のすべての出現箇所を置換することです。

このように

// replace all dashes by a colon
alert('12-34-56'.replaceAll("-", ":")) // 12:34:56

regexp.exec(str)

regexp.exec(str)メソッドは、文字列str内のregexpのマッチを返します。前のメソッドとは異なり、文字列ではなく正規表現に対して呼び出されます。

正規表現にフラグgがあるかどうかによって、動作が異なります。

gがない場合、regexp.exec(str)str.match(regexp)とまったく同じように最初のマッチを返します。この動作は新しいものを何ももたらしません。

しかし、フラグgがある場合、

  • regexp.exec(str)の呼び出しは、最初のマッチを返し、その直後の位置をプロパティregexp.lastIndexに保存します。
  • 次の呼び出しは、位置regexp.lastIndexから検索を開始し、次のマッチを返し、その後の位置をregexp.lastIndexに保存します。
  • ...など。
  • マッチがない場合、regexp.execnullを返し、regexp.lastIndex0にリセットします。

そのため、繰り返しの呼び出しは、プロパティregexp.lastIndexを使用して現在の検索位置を追跡することで、すべてのマッチを次々に返します。

以前、str.matchAllメソッドがJavaScriptに追加される前は、ループ内でregexp.execの呼び出しを使用して、グループを含むすべてのマッチを取得していました。

let str = 'More about JavaScript at https://javascriptinfo.dokyumento.jp';
let regexp = /javascript/ig;

let result;

while (result = regexp.exec(str)) {
  alert( `Found ${result[0]} at position ${result.index}` );
  // Found JavaScript at position 11, then
  // Found javascript at position 33
}

これは現在でも機能しますが、新しいブラウザでは通常、str.matchAllの方が便利です。

lastIndexを手動で設定することで、指定された位置から検索するためにregexp.execを使用できます。

例えば

let str = 'Hello, world!';

let regexp = /\w+/g; // without flag "g", lastIndex property is ignored
regexp.lastIndex = 5; // search from 5th position (from the comma)

alert( regexp.exec(str) ); // world

正規表現にフラグyがある場合、検索は正確に位置regexp.lastIndexで実行され、それ以上は実行されません。

上記の例でフラグgyに置き換えてみましょう。位置5に単語がないため、マッチはありません。

let str = 'Hello, world!';

let regexp = /\w+/y;
regexp.lastIndex = 5; // search exactly at position 5

alert( regexp.exec(str) ); // null

これは、文字列から正規表現によって何かを正確な位置で「読み取る」必要がある場合に便利です。

regexp.test(str)

regexp.test(str)メソッドは、マッチを検索し、それが存在するかどうかをtrue/falseで返します。

例えば

let str = "I love JavaScript";

// these two tests do the same
alert( /love/i.test(str) ); // true
alert( str.search(/love/i) != -1 ); // true

否定的な回答の例

let str = "Bla-bla-bla";

alert( /love/i.test(str) ); // false
alert( str.search(/love/i) != -1 ); // false

正規表現にフラグgがある場合、regexp.testregexp.execと同様に、regexp.lastIndexプロパティから検索し、このプロパティを更新します。

そのため、指定された位置から検索するために使用できます。

let regexp = /love/gi;

let str = "I love JavaScript";

// start the search from position 10:
regexp.lastIndex = 10;
alert( regexp.test(str) ); // false (no match)
異なるソースで繰り返しテストされた同じグローバル正規表現が失敗する可能性があります。

同じグローバル正規表現を異なる入力に適用すると、regexp.testの呼び出しによってregexp.lastIndexプロパティが進むため、別の文字列の検索がゼロ以外の位置から開始される可能性があり、間違った結果につながる可能性があります。

例えば、ここでは同じテキストに対してregexp.testを2回呼び出していますが、2回目は失敗します。

let regexp = /javascript/g;  // (regexp just created: regexp.lastIndex=0)

alert( regexp.test("javascript") ); // true (regexp.lastIndex=10 now)
alert( regexp.test("javascript") ); // false

これはまさに、2回目のテストでregexp.lastIndexがゼロ以外であるためです。

これを回避するには、各検索の前にregexp.lastIndex = 0を設定します。または、正規表現のメソッドを呼び出す代わりに、文字列メソッドstr.match/search/...を使用します。これらのメソッドはlastIndexを使用しません。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善のための提案がある場合は、コメントする代わりにGitHubのissueまたはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数語のコードを挿入するには、<code>タグを使用します。数行の場合は、<pre>タグで囲みます。10行を超える場合は、サンドボックス(plnkrjsbincodepenなど)を使用します。