6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2021年11月27日 JavaScript (JS Primer) 正規表現

正規表現による検索

(続き)

正規表現によるマッチした文字列の取得

文字列による検索では、検索した文字列そのものがマッチした文字列になります。 しかし、searchメソッドの正規表現による検索は、正規表現パターンによる検索であるため、検索してマッチした文字列の長さは固定ではありません。 つまり、次のようにString#searchメソッドでマッチしたインデックスのみを取得しても、実際にマッチした文字列がわかりません。

const str = "abc123def";
// 連続した数字にマッチする正規表現
const searchPattern = /\d+/;
const index = str.search(searchPattern); // => 3
// `index` だけではマッチした文字列の長さがわからない
str.slice(index, index + マッチした文字列の長さ); // マッチした文字列は取得できない

そのため、マッチした文字列を取得するString#matchメソッドとString#matchAllメソッドが用意されています。 また、これらのメソッドは正規表現のマッチを文字列の最後まで繰り返すgフラグ(globalの略称)によって挙動が変わります。

マッチした文字列の取得 まずは、マッチした文字列を取得するString#matchメソッドから見ていきます。 String#matchメソッドは、正規表現の/パターン/が"文字列"にマッチすると、マッチした文字列に関する情報を返すメソッドです。

"文字列".match(/パターン/);

String#matchメソッドで検索した結果、正規表現にマッチする文字列がなかった場合はnullを返します。

console.log("文字列".match(/マッチしないパターン/)); // => null

gフラグなしのパターン検索で検索

String#matchメソッドは正規表現のgフラグなしのパターンで検索した場合、最初にマッチしたものが見つかった時点で検索が終了します。 このときのmatchメソッドの返り値は、indexプロパティとinputプロパティをもった特殊な配列となります。 indexプロパティにはマッチした文字列の先頭のインデックスが、inputプロパティには検索対象となった文字列全体が含まれています。

次のコードの/[a-zA-Z]+/という正規表現はaからZのどれかの文字が1つ以上連続しているものにマッチします。 この正規表現にマッチした文字列は、返り値の配列からインデックスアクセスで取得できます。 gフラグなしでは、最初にマッチしたものを見つけた時点で検索が終了するので、返り値の配列には1つの要素しか含まれていません。

const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/;
// gフラグなしでは、最初の結果のみを含んだ特殊な配列を返す
const results = str.match(alphabetsPattern);
resultsの中身を出力した結果↓
[ 'ABC', index: 0, input: 'ABC あいう DE えお', groups: undefined ]

console.log(results.length); // => 1
// マッチした文字列はインデックスでアクセスできる
console.log(results[0]); // => "ABC"
// マッチした文字列の先頭のインデックス
console.log(results.index); // => 0
// 検索対象となった文字列全体
console.log(results.input); // => "ABC あいう DE えお"

resultsの中身をconsole.logで出力した結果↓
[ 'ABC', index: 0, input: 'ABC あいう DE えお', groups: undefined ]

gフラグありのパターンで検索

String#matchメソッドは正規表現のgフラグありのパターンで検索した場合、マッチしたすべての文字列を含んだ配列を返します。

次のコードの/[a-zA-Z]+/gという正規表現はaからZのどれかの文字が1つ以上連続しているものに繰り返しマッチします。 この正規表現にマッチする箇所は"ABC"と"DE"の2つとなるため、String#matchメソッドの返り値である配列にも2つの要素が含まれています。

const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/g;
// gフラグありでは、すべての検索結果を含む配列を返す
const resultsWithG = str.match(alphabetsPattern);
console.log(resultsWithG.length); // => 2
console.log(resultsWithG[0]); // => "ABC"
console.log(resultsWithG[1]); // => "DE"
// indexとinputはgフラグありの場合は追加されない
console.log(resultsWithG.index); // => undefined
console.log(resultsWithG.input); // => undefined

このときのmatchメソッドの返り値である配列にはindexとinputプロパティはありません。 なぜなら、複数の箇所にマッチする場合においては、1つのindexプロパティでは意味が一意に決まらないためです。

↓resultsWithGの中身をconsole.logで出力した結果
[ 'ABC', 'DE' ]

String#matchメソッドの挙動を一旦まとめると次のようになります。

  • 正規表現gフラグの有無に関わらず、マッチしない場合は、nullを返します。
  • 正規表現gフラグがない場合は、マッチした文字列を含んだ特殊な配列を返します。
  • 正規表現gフラグがある場合は、マッチしたすべての結果を含んだただの配列を返します。

String#matchAllメソッド

matchAllメソッドは正規表現パターンとマッチした結果をIteratorオブジェクトで返します。 以下のコードではアルファベットにマッチした結果をmatchesIteratorに格納してfor...of構文で反復処理しています。

const str = "ABC あいう DE えお";
const alphabetsPattern = /[a-zA-Z]+/g;
// matchAllはIteratorを返す
const matchesIterator = str.matchAll(alphabetsPattern);
console.log(matchesIterator);
for (const match of matchesIterator) {
    // マッチした要素ごとの情報を含んでいる
    console.log(`match: "${match[0]}", index: ${match.index}, input: "${match.input}"`);
}
// 次の順番でコンソールに出力される
// match: "ABC", index: 0, input: "ABC あいう DE えお"
// match: "DE", index: 8, input: "ABC あいう DE えお"

イテレーターで返すとありましたがわからかったので深堀りしました。

名称 説明 持っているメソッド・プロパティ
イテラブルなオブジェクト イテレータを持つオブジェクト Symbol.iterator
イテレータ 順番にイテレータリザルトを取り出すことのできるオブジェクト .next()
イテレータリザルト 取り出した値や、取り出し終えたかどうかの真偽値を持つオブジェクト .value, .done

画像引用

今回の場合では正規表現パターンとマッチした文字列とプロパティをイテレータとして変数に代入しているため、for...ofで繰り返し処理をすることができます。

参考

JSprimer 正規表現

JavaScript の イテレータ を極める!