この章では、ECMAScript 6における正規表現の新機能について説明します。ES5の正規表現機能とUnicodeに精通していると理解しやすいでしょう。必要に応じて、「Speaking JavaScript」の以下の2つの章を参照してください。
/y (スティッキー)RegExp.prototype.exec(str)RegExp.prototype.test(str)String.prototype.search(regex)String.prototype.match(regex)String.prototype.split(separator, limit)String.prototype.replace(search, replacement)/u (Unicode).) は、コードユニットではなくコードポイントと一致するflagsRegExp() をコピーコンストラクタとして使用できるexec() の反復可能なバージョンECMAScript 6では、以下の正規表現機能が新しく追加されました。
/y (スティッキー) は、正規表現の各一致を前回の一致の末尾に固定します。/u (Unicode) は、サロゲートペア (\uD83D\uDE80 など) をコードポイントとして処理し、正規表現でUnicodeコードポイントエスケープ (\u{1F680} など) を使用できるようにします。flags を使用すると、ES5ですでにパターンへのアクセスを提供している source と同様に、正規表現のフラグにアクセスできます。 > /abc/ig.source // ES5
'abc'
> /abc/ig.flags // ES6
'gi'
RegExp() を使用して、正規表現のコピーを作成できます。 > new RegExp(/abc/ig).flags
'gi'
> new RegExp(/abc/ig, 'i').flags // change flags
'i'
/y (スティッキー) 新しいフラグ /y は、文字列に対して正規表現 re を照合する際に、2つの点を変更します。
re.lastIndex に固定: 一致は re.lastIndex (前回の一致後のインデックス) から開始する必要があります。この動作は ^ アンカーに似ていますが、このアンカーでは、一致は常にインデックス0から開始する必要があります。re.lastIndex は一致後のインデックスに設定されます。この動作は /g フラグに似ています。/g と同様に、/y は通常、複数回一致させるために使用されます。このマッチング動作の主なユースケースは、各一致が直前の後続に続くようにするトークン化です。スティッキー正規表現と exec() によるトークン化の例は後ほど示します。
さまざまな正規表現操作が /y フラグにどのように反応するかを見てみましょう。次の表は概要を示しています。詳細は後ほど説明します。
正規表現のメソッド (re はメソッドが呼び出される正規表現です)
| フラグ | マッチング開始 | 固定対象 | 一致した場合の結果 | 一致しない場合 | re.lastIndex | |
|---|---|---|---|---|---|---|
| exec() | – | 0 | – | マッチオブジェクト | null | 変更なし |
| /g | re.lastIndex |
– | マッチオブジェクト | null | 一致後のインデックス | |
| /y | re.lastIndex |
re.lastIndex |
マッチオブジェクト | null | 一致後のインデックス | |
| /gy | re.lastIndex |
re.lastIndex |
マッチオブジェクト | null | 一致後のインデックス | |
| test() | (任意) | (exec()と同様) | (exec()と同様) | true | false | (exec()と同様) |
文字列のメソッド (str はメソッドが呼び出される文字列、r は正規表現パラメータです)
| フラグ | マッチング開始 | 固定対象 | 一致した場合の結果 | 一致しない場合 | r.lastIndex | |
|---|---|---|---|---|---|---|
| search() | –, /g | 0 | – | 一致のインデックス | -1 | 変更なし |
| /y, /gy | 0 | 0 | 一致のインデックス | -1 | 変更なし | |
| match() | – | 0 | – | マッチオブジェクト | null | 変更なし |
| /y | r.lastIndex |
r.lastIndex |
マッチオブジェクト | null | 一致後 | |
| のインデックス | ||||||
| /g | 前の後 | – | 一致を含む配列 | null | 0 | |
| 一致 (ループ) | ||||||
| /gy | 前の後 | 前の後 | 一致を含む配列 | null | 0 | |
| 一致 (ループ) | のインデックス | |||||
| split() | –, /g | 前の後 | – | 文字列の配列 | [str] |
変更なし |
| 一致 (ループ) | 一致間 | |||||
| /y, /gy | 前の後 | 前の後 | 空文字列を含む配列 | [str] |
変更なし | |
| 一致 (ループ) | のインデックス | 一致間 | ||||
| replace() | – | 0 | – | 最初の一致を置換 | 置換なし | 変更なし |
| /y | 0 | 0 | 最初の一致を置換 | 置換なし | 変更なし | |
| /g | 前の後 | – | すべての一致を置換 | 置換なし | 変更なし | |
| 一致 (ループ) | ||||||
| /gy | 前の後 | 前の後 | すべての一致を置換 | 置換なし | 変更なし | |
| 一致 (ループ) | のインデックス |
RegExp.prototype.exec(str) /g が設定されていない場合、マッチングは常に先頭から開始されますが、一致が見つかるまでスキップします。 REGEX.lastIndex は変更されません。
const REGEX = /a/;
REGEX.lastIndex = 7; // ignored
const match = REGEX.exec('xaxa');
console.log(match.index); // 1
console.log(REGEX.lastIndex); // 7 (unchanged)
/g が設定されている場合、マッチングは REGEX.lastIndex から開始され、一致が見つかるまでスキップします。 REGEX.lastIndex は一致後の位置に設定されます。つまり、exec() が null を返すまでループすると、すべての一致を受け取ります。-
const REGEX = /a/g;
REGEX.lastIndex = 2;
const match = REGEX.exec('xaxa');
console.log(match.index); // 3
console.log(REGEX.lastIndex); // 4 (updated)
// No match at index 4 or later
console.log(REGEX.exec('xaxa')); // null
/y のみが設定されている場合、マッチングは REGEX.lastIndex から開始され、その位置に固定されます (一致が見つかるまでスキップしません)。 REGEX.lastIndex は /g が設定されている場合と同様に更新されます。
const REGEX = /a/y;
// No match at index 2
REGEX.lastIndex = 2;
console.log(REGEX.exec('xaxa')); // null
// Match at index 3
REGEX.lastIndex = 3;
const match = REGEX.exec('xaxa');
console.log(match.index); // 3
console.log(REGEX.lastIndex); // 4
/y と /g の両方を設定することは、/y のみを設定することと同じです。
RegExp.prototype.test(str) test() は exec() と同じように動作しますが、マッチングが成功または失敗したときに true または false を返します (マッチオブジェクトまたは null ではなく)。
const REGEX = /a/y;
REGEX.lastIndex = 2;
console.log(REGEX.test('xaxa')); // false
REGEX.lastIndex = 3;
console.log(REGEX.test('xaxa')); // true
console.log(REGEX.lastIndex); // 4
String.prototype.search(regex) search() はフラグ /g と lastIndex を無視します (これも変更されません)。文字列の先頭から開始して、最初の一致を探し、そのインデックスを返します (一致がない場合は -1 を返します)。
const REGEX = /a/;
REGEX.lastIndex = 2; // ignored
console.log('xaxa'.search(REGEX)); // 1
フラグ /y を設定した場合、lastIndex は依然として無視されますが、正規表現はインデックス0に固定されるようになります。
const REGEX = /a/y;
REGEX.lastIndex = 1; // ignored
console.log('xaxa'.search(REGEX)); // -1 (no match)
String.prototype.match(regex) match() には2つのモードがあります。
/g が設定されていない場合、exec() と同様に動作します。/g が設定されている場合、一致した文字列部分を含む配列、または null を返します。-フラグ /g が設定されていない場合、match() は exec() と同様にグループをキャプチャします。
{
const REGEX = /a/;
REGEX.lastIndex = 7; // ignored
console.log('xaxa'.match(REGEX).index); // 1
console.log(REGEX.lastIndex); // 7 (unchanged)
}
{
const REGEX = /a/y;
REGEX.lastIndex = 2;
console.log('xaxa'.match(REGEX)); // null
REGEX.lastIndex = 3;
console.log('xaxa'.match(REGEX).index); // 3
console.log(REGEX.lastIndex); // 4
}
フラグ /g のみが設定されている場合、match() はすべての一致する部分文字列を配列 (または null) で返します。マッチングは常に位置0から開始されます。-
const REGEX = /a|b/g;
REGEX.lastIndex = 7;
console.log('xaxb'.match(REGEX)); // ['a', 'b']
console.log(REGEX.lastIndex); // 0
さらにフラグ /y を設定すると、マッチングは繰り返し実行されますが、正規表現は前回の一致 (または0) 後のインデックスに固定されます。
const REGEX = /a|b/gy;
REGEX.lastIndex = 0; // ignored
console.log('xab'.match(REGEX)); // null
REGEX.lastIndex = 1; // ignored
console.log('xab'.match(REGEX)); // null
console.log('ab'.match(REGEX)); // ['a', 'b']
console.log('axb'.match(REGEX)); // ['a']
String.prototype.split(separator, limit) split() の詳細は Speaking JavaScript で説明されています。
ES6では、フラグ /y を使用した場合にどのように変化するかを確認することが重要です。
/y を使用すると、文字列は区切り文字で始まる必要があります。
> 'x##'.split(/#/y) // no match
[ 'x##' ]
> '##x'.split(/#/y) // 2 matches
[ '', '', 'x' ]
後続の区切り文字は、最初の区切り文字の直後に続く場合にのみ認識されます。
> '#x#'.split(/#/y) // 1 match
[ '', 'x#' ]
> '##'.split(/#/y) // 2 matches
[ '', '', '' ]
つまり、最初の区切り文字の前の文字列と区切り文字の間の文字列は常に空になります。
通常どおり、グループを使用して区切り文字の一部を結果配列に入れることができます。
> '##'.split(/(#)/y)
[ '', '#', '', '#', '' ]
String.prototype.replace(search, replacement) フラグ /g がない場合、replace() は最初の一致のみを置換します。
const REGEX = /a/;
// One match
console.log('xaxa'.replace(REGEX, '-')); // 'x-xa'
/y のみが設定されている場合も、最大で1つの一致が得られますが、その一致は常に文字列の先頭に固定されます。 lastIndex は無視され、変更されません。
const REGEX = /a/y;
// Anchored to beginning of string, no match
REGEX.lastIndex = 1; // ignored
console.log('xaxa'.replace(REGEX, '-')); // 'xaxa'
console.log(REGEX.lastIndex); // 1 (unchanged)
// One match
console.log('axa'.replace(REGEX, '-')); // '-xa'
/g が設定されている場合、replace() はすべての一致を置換します。
const REGEX = /a/g;
// Multiple matches
console.log('xaxa'.replace(REGEX, '-')); // 'x-x-'
/gy が設定されている場合、replace() はすべての一致を置換しますが、各一致は前回の一致の末尾に固定されます。
const REGEX = /a/gy;
// Multiple matches
console.log('aaxa'.replace(REGEX, '-')); // '--xa'
パラメータ replacement は関数にすることもできます。 詳細は「Speaking JavaScript」を参照してください。
スティッキーマッチングの主なユースケースは、テキストをトークンのシーケンスに変換する*トークン化*です。トークン化の重要な特徴の1つは、トークンがテキストの断片であり、それらの間にギャップがあってはならないことです。したがって、スティッキーマッチングはここで最適です。
function tokenize(TOKEN_REGEX, str) {
const result = [];
let match;
while (match = TOKEN_REGEX.exec(str)) {
result.push(match[1]);
}
return result;
}
const TOKEN_GY = /\s*(\+|[0-9]+)\s*/gy;
const TOKEN_G = /\s*(\+|[0-9]+)\s*/g;
トークンの有効なシーケンスでは、スティッキーマッチングとスティッキーでないマッチングは同じ出力を生成します。
> tokenize(TOKEN_GY, '3 + 4')
[ '3', '+', '4' ]
> tokenize(TOKEN_G, '3 + 4')
[ '3', '+', '4' ]
ただし、文字列にトークン以外のテキストがある場合、スティッキーマッチングはトークン化を停止しますが、スティッキーでないマッチングはトークン以外のテキストをスキップします。
> tokenize(TOKEN_GY, '3x + 4')
[ '3' ]
> tokenize(TOKEN_G, '3x + 4')
[ '3', '+', '4' ]
トークン化中のスティッキーマッチングの動作は、エラー処理に役立ちます。
スティッキーマッチングを手動で実装する場合、次のようにします。関数 execSticky() は、スティッキーモードの RegExp.prototype.exec() と同様に動作します。
function execSticky(regex, str) {
// Anchor the regex to the beginning of the string
let matchSource = regex.source;
if (!matchSource.startsWith('^')) {
matchSource = '^' + matchSource;
}
// Ensure that instance property `lastIndex` is updated
let matchFlags = regex.flags; // ES6 feature!
if (!regex.global) {
matchFlags = matchFlags + 'g';
}
const matchRegex = new RegExp(matchSource, matchFlags);
// Ensure we start matching `str` at `regex.lastIndex`
const matchOffset = regex.lastIndex;
const matchStr = str.slice(matchOffset);
let match = matchRegex.exec(matchStr);
// Translate indices from `matchStr` to `str`
regex.lastIndex = matchRegex.lastIndex + matchOffset;
match.index = match.index + matchOffset;
return match;
}
/u (Unicode) フラグ /u は、正規表現の特別なUnicodeモードをオンにします。このモードには2つの機能があります。
\u{1F42A} などのUnicodeコードポイントエスケープシーケンスを使用できます。 \u03B1 などの通常のUnicodeエスケープは、4桁の16進数 (基本多言語面に相当) の範囲しかありません。Unicodeに関する章のセクションには、エスケープシーケンスに関する詳細情報があります。次に、機能2の結果について説明します。Unicodeコードポイントエスケープ (例: \u{1F680}) の代わりに、2つのUTF-16コードユニット (例: \uD83D\uDE80}) を使用しています。これにより、サロゲートペアがUnicodeモードでグループ化され、Unicodeモードと非Unicodeモードの両方で機能することが明確になります。
> '\u{1F680}' === '\uD83D\uDE80' // code point vs. surrogate pairs
true
非Unicodeモードでは、正規表現内の単一のサロゲートは、(サロゲートペアエンコーディング) コードポイント内でも見つかります。
> /\uD83D/.test('\uD83D\uDC2A')
true
Unicodeモードでは、サロゲートペアはアトミックユニットになり、単一のサロゲートは「内部」では見つかりません。
> /\uD83D/u.test('\uD83D\uDC2A')
false
実際の単一のサロゲートはまだ見つかります。
> /\uD83D/u.test('\uD83D \uD83D\uDC2A')
true
> /\uD83D/u.test('\uD83D\uDC2A \uD83D')
true
Unicode モードでは、文字クラスにコードポイントを記述できます。そして、それらはもはや2文字として解釈されません。
> /^[\uD83D\uDC2A]$/u.test('\uD83D\uDC2A')
true
> /^[\uD83D\uDC2A]$/.test('\uD83D\uDC2A')
false
> /^[\uD83D\uDC2A]$/u.test('\uD83D')
false
> /^[\uD83D\uDC2A]$/.test('\uD83D')
true
.)はコードユニットではなく、コードポイントにマッチする Unicode モードでは、ドット演算子はコードポイント(1つまたは2つのコードユニット)にマッチします。非 Unicode モードでは、単一のコードユニットにマッチします。例えば
> '\uD83D\uDE80'.match(/./gu).length
1
> '\uD83D\uDE80'.match(/./g).length
2
Unicode モードでは、量指定子はコードポイント(1つまたは2つのコードユニット)に適用されます。非 Unicode モードでは、単一のコードユニットに適用されます。例えば
> /\uD83D\uDE80{2}/u.test('\uD83D\uDE80\uD83D\uDE80')
true
> /\uD83D\uDE80{2}/.test('\uD83D\uDE80\uD83D\uDE80')
false
> /\uD83D\uDE80{2}/.test('\uD83D\uDE80\uDE80')
true
flags ECMAScript 6 では、正規表現は以下のデータプロパティを持ちます。
sourceflagsglobal、ignoreCase、multiline、sticky、unicodelastIndexちなみに、lastIndex は現在唯一のインスタンスプロパティです。他のすべてのデータプロパティは、get RegExp.prototype.global のような内部インスタンスプロパティとゲッターを介して実装されています。
プロパティ source(ES5ですでに存在していました)には、正規表現パターンが文字列として含まれています。
> /abc/ig.source
'abc'
プロパティ flags は新しく、フラグを文字列として含み、フラグごとに1文字が含まれています。
> /abc/ig.flags
'gi'
既存の正規表現のフラグを変更することはできません(ignoreCase などは常に不変です)。しかし、flags を使用すると、フラグが変更されたコピーを作成できます。
function copyWithIgnoreCase(re) {
return new RegExp(re.source,
re.flags.includes('i') ? re.flags : re.flags+'i');
}
次のセクションでは、正規表現の変更されたコピーを作成する別の方法について説明します。
RegExp() はコピーコンストラクターとして使用できる ES6 では、コンストラクター RegExp() には2つのバリアントがあります(2つ目は新しいものです)。
new RegExp(pattern : string, flags = '')pattern で指定されたとおりに、新しい正規表現が作成されます。flags がない場合は、空の文字列 '' が使用されます。new RegExp(regex : RegExp, flags = regex.flags)regex が複製されます。flags が指定されている場合、コピーのフラグを決定します。以下のインタラクションは後者のバリアントを示しています。
> new RegExp(/abc/ig).flags
'gi'
> new RegExp(/abc/ig, 'i').flags // change flags
'i'
したがって、RegExp コンストラクターはフラグを変更する別の方法を提供します。
function copyWithIgnoreCase(re) {
return new RegExp(re,
re.flags.includes('i') ? re.flags : re.flags+'i');
}
exec() の反復可能なバージョン 以下の関数 execAll() は、exec() の反復可能なバージョンであり、正規表現のすべての一致を取得するために exec() を使用することによるいくつかの問題を修正します。
exec() が null を返すまで呼び出します)。exec() は正規表現を変更します。これは、副作用が問題になる可能性があることを意味します。/g を設定する必要があります。設定しないと、最初の一致のみが返されます。function* execAll(regex, str) {
// Make sure flag /g is set and regex.index isn’t changed
const localCopy = copyAndEnsureFlag(regex, 'g');
let match;
while (match = localCopy.exec(str)) {
yield match;
}
}
function copyAndEnsureFlag(re, flag) {
return new RegExp(re,
re.flags.includes(flag) ? re.flags : re.flags+flag);
}
execAll() の使用
const str = '"fee" "fi" "fo" "fum"';
const regex = /"([^"]*)"/;
// Access capture of group #1 via destructuring
for (const [, group1] of execAll(regex, str)) {
console.log(group1);
}
// Output:
// fee
// fi
// fo
// fum
以下の文字列メソッドは、正規表現メソッドに作業の一部を委任するようになりました。
String.prototype.match は RegExp.prototype[Symbol.match] を呼び出します。String.prototype.replace は RegExp.prototype[Symbol.replace] を呼び出します。String.prototype.search は RegExp.prototype[Symbol.search] を呼び出します。String.prototype.split は RegExp.prototype[Symbol.split] を呼び出します。詳細については、文字列の章の「正規表現の作業をパラメータに委任する文字列メソッド」セクションを参照してください。