15장 정규 표현식 업데이트

2023.06.16

15.1 플래그 속성

  • ES2015에서 정규 표현식은 표현식에 대한 플래그가 포함된 문자열을 반환하는 flags 접근자 속성을 갖는다.
  • flags 속성 이전에는 RegExp 객체에 어떤 플래그가 있는지 알 수 있는 유일한 방법은 개별 플래그(rex.global, rex.multiline 등)를 반영하는 개별 속성을 보거나 toString 메서드를 사용하여 문자열의 끝을 봐야 했다.
  • flags 속성을 사용하면 문자열로 직접 사용할 수 있다.
1const rex = /example/gi;
2console.log(rex.flags); // "gi"
  • 사양에서는 gimsuy 표현식이 생성될 때 플래그가 어떻게 지정되었는지에 관계없이 알파벳 순서로 플래그가 제공되도록 정의되어 있다.

15.2 새로운 플래그

  • ES2015 ES2018에서 TC39는 새로운 정규 표현식 모드 플래그를 추가했다.
    • y: 고정(sticky) 플래그(ES2015)는 정규 표현식이 문자열에서 정규 표현식 객체의 마지막 인덱스(lastIndex)에서 시작하는 것과만 일치함을 의미한다
    • U: 유니코드(Unicode) 플래그(ES2015)는 기본적으로 비활성화되어 있는 다양한 유니코드 기능을 활성화한다.
    • s: 만능 점(dot all) 플래그(ES2018)는 "어느 문자" 토큰(.)도 줄 바꿈 문자에 일치시킨다.

15.2.1 스티키 플래그

  • y플래그는 문자열에 대해 정규 표현식을 평가할 때 자바스크립트 엔진이 문자열 전체를 검색하지 않고 정규 표현식 객체의 마지막 인덱스(lastIndex)에서 시작하는 문자열의 일치 엔트리만 확인한다는 것을 의미한다.
1function tryRex(rex, str) {
2  console.log(`lastIndex: ${rex.lastIndex}`);
3  const match = rex.exec(str);
4  if (match) {
5    console.log(`Match:     ${match[0]}`);
6    console.log(`At:        ${match.index}`);
7  } else {
8    console.log('No match');
9  }
10}
11
12const str = 'this is a test';
13
14// 스티키가 아닐 때, 전체 문자열을 검색한다.
15tryRex(/test/, str);
16// lastIndex: 0
17// Match:     test
18// At:        10
19
20// 스티키, 검색하지 못함. lastIndex에서 일치해야만 한다.
21const rex1 = /test/y; // `rex.lastIndex` defaults to 0
22tryRex(rex1, str);
23// lastIndex: 0
24// No match
25
26const rex2 = /test/y;
27rex2.lastIndex = 10; // 일치시키기 원하는 곳으로 설정한다.
28tryRex(rex2, str);
29// lastIndex: 10
30// Match:     test
31// At:        10
  • 이는 토큰별로 문자열 토큰을 이동하고 구문 분석과 같이 가능한 토큰 패턴(정규 표현식) 집합과 일치하는지 확인할 때 편리하다.
  • 스티키 플래그가 추가되기 전에 이를 수행하려면 표현식 시작 부분에 (입력 시작) 앵커를 사용하고 일치를 수행하기 전에 문자열의 시작 부분에 있도록 문자열에서 이미 처리한 문자를 잘라야 했다.
  • 스티키 플래그는 더 간단하고 잘린 문자열을 생성하지 않도록 하여 프로세스를 더 효율적으로 만든다.
  • flags를 살펴보거나 플래그가 설정된 경우 true인 표현식의 sticky 속성을 확인하여 스티키 플래그가 설정되었는지 확인할 수 있다.

15.2.2 유니코드 플래그(u)

  • ES2015는 정규 표현식을 포함하여 많은 영역에서 자바스크립트의 유니코드 지원을 개선했다.
  • 기존 코드에 대한 문제를 방지하기 위해 정규 표현식의 새로운 유니코드 기능은 기본적으로 비활성화되고 u 플래그로 활성화된다.
  • flags를 살펴보거나 표현식의 unicode 속성을 확이니하여 유니코드 플래그가 설정되었는지 확인할 수 있다.

15.2.3 “만능 점” 플래그(s)

  • ES2018은 자바스크립트의 정규 표현식에 s 플래그("dotAll")를 추가했다.
  • 많은 종류의 정규 표현식(자바스크립트 포함)으로 인해 "어느 문자" 토큰(.)이 \r\n을 일치시키지 않는다.
  • "만능 점"은 이 동작을 수정하는 일반적인 해결책이다.
  • ES2018 이전에는 자바스크립트가 지원하지 않았기 때문에 사람들이 [\s\s](공백이 있거나 없는 모든 것), [\w\W](단어이거나 아닌 모든 것)와 같은 해결 방법을 사용해야 했다.
  • 또는 자바스크립트에 특화된 [^](빈 부정 문자 클래스, "아무것도 아님"이 "아무것"과 동일함) 등이 있다.
1const str = 'Testing\nAlpha\nBravo\nCharlie\nJavaScript';
2console.log(str.match(/.[A-Z]/g)); // ["aS"]
3console.log(str.match(/.[A-Z]/gs)); // ["\nA", "\nB", "\nC", "aS"]
  • 예에서 s 플래그가 없으면 "JavaScript"의 "as"만 일치하는 것을 볼 수 있다.
  • 플래그를 사용하여 "Alpha"의 "A", "Bravo"의 "B", "Charlie"의 "C" 앞에 있는 줄 바꿈 문자도 일치했다.
  • flags를 살펴보거나 플래그가 설정될 때 tru인 dotAll 속성을 확인하여 표현식에 "만능 점” 플래그가 설정되었는지 확인할 수 있다.

15.3 명명된 캡처 그룹

  • ES2018은 명명된 캡처 그룹을 자바스크립트의 정규 표현식에 추가하여 기존의 익명 캡처 그룹에 합류시켰다.
  • 명명된 캡처 그룹은 다음 양식으로 작성된다.
1(?<name>pattern)
  • 그를 시작 부분에 있는 물음표(?) 바로 뒤에 그룹 이름을 꺽쇠 괄호로 묶는다.
  • 명명된 캡처 그룹은 익명 캡처
  • 그들과 똑같이 작동하므로 일치 결과(result[1] 등)에서, 나중에 식(\1등)에서 역참조로, replace($1 등) 사용 시 대체 토큰에서 접근할 수 있다.

15.3.1 기본 기능

  • 명명된 캡처 그룹은 일치 결과의 일반적인 위치와 결과의 새 그룹 객체에 그룹 이름을 사용하는 속성으로 나타난다.
1// Not a "new toy," just sets up the example for the next snippet using a named capture group
2const rex = /testing (\d+)/g;
3const result = rex.exec('This is a test: testing 123 testing');
4console.log(result[0]); // testing 123
5console.log(result[1]); // 123
6console.log(result.index); // 16
7console.log(result.input); // This is a test: testing 123 testing
  • 일치가 성공했기 때문에 결과는 인덱스 0에 전체 일치 텍스트, 인덱스 1에 캡처 그룹, 인덱스 속성에 있는 일치 인덱스와 input 속성으로 일치 작업에 대한 입력이 있는 기능이 더해진 배열이다.
  • 캡처 그룹의 값은 여전히 인덱스 1에 표시되지만 이름이 지정된 각 캡처 그룹(이 경우에는 하나 number)에 대한 속성이 있는 객체인 새 속성 groups에 주목하자.
  • 아래는 number라는 명명된 캡처 그룹을 사용한 예시이다.
1const rex = /testing (?<number>\d+)/g;
2const result = rex.exec('This is a test: testing 123 testing');
3console.log(result[0]); // testing 123
4console.log(result[1]); // 123
5console.log(result.index); // 16
6console.log(result.input); // This is a test: testing 123 testing
7console.log(result.groups); // {"number": "123"}
  • 새 groups 객체에는 Object.create(null)로 생성된 것처럼 프로토타입이 없다. 그래서 대부분의 객체가 toString과 hasOwnProperty와 같은 Object.prototype에서 상속하는 속성을 포함하여 속성이 전혀 없다.
  • 명명된 캡처 그룹의 이름과 Object.prototype에 의해 정의된 속성 간의 충돌 가능성에 대해 걱정할 필요가 없다.

15.3.2 역참조

  • 명명된 그룹을 사용하면 역참조(backreference)를 더 명확하고 쉽게 유지 관리할 수 있다.
  • 표현식은 선행 따옴표에 캡처 그룹(([”’]))을 사용하고 후행 따옴표에 역분 조(\1)를 사용하여 큰 따옴표 또는 작은 따옴표로 묶인 텍스트를 찾는다.
1// Not a new toy, this snippet shows the anonymous version of the named backreference
2// shown by the next snippet
3const rex = /(["']).+?\1/g;
4const str = "testing 'a one', \"and'a two\", and'a three";
5console.log(str.match(rex)); // ["'a one'", "\"and'a two\""]
  • 역참조가 첫 번째 엔트리의 양쪽에 작은따옴표가 있고 두 번째 엔트리의 양쪽에 큰따음표가 있다는 사실을 확인하자.
  • 명명된 캡처 그룹을 사용하면 다시 언급하는 내용을 더 명확하게 알 수 있다. 명명된 역참조는 \k<name> 양식이다.
1const rex = /(?<quote>["']).+?\k<quote>/g;
2const str = "testing 'a one', \"and'a two\", and'a three";
3console.log(str.match(rex)); // ["'a one'", "\"and'a two\""]
  • 이제 역참조가 참조하는 내용을 알기 위해 캡처 그룹을 계산할 필요가 없다. 이름이 쉽게 만들어 준다.
  • 마지막으로, 명확성을 위해 명명된 캡처 그룹과 함께 명명된 역참조를 사용하는 것이 가장 좋지만, 명명된 캡처 그룹은 익명의 캡처 그룹에 더불어 추가적인 이름 기능이 있으므로 이전 익명 양식(예: \1)으로 명명된 캡처 그룹을 참조할 수 있다.

15.3.3 대체 토큰

  • 정규 표현식으로 대체할 때(일반적으로 String.prototype.replace를 통해) $1, $2 등과 같은 토큰을 통해 캡처 그룹을 참조하는 익숙한 방법 외에도 $<name> 양식의 명명된 토큰을 사용할 수 있 다.
  • 예를 들어 문자열의 날짜를 yyyy-mm-dd 양식에서 일반적인 유럽 dd/mm/yyyy 양식으로 변환하려는 경우 다음과 같다.
1const rex = /^(?<year>\d{2}|\d{4})[-\/](?<month>\d{1,2})[-\/](?<day>\d{1,2})$/;
2const str = '2019-02-14'.replace(rex, '$<day>/$<month>/$<year>');
3console.log(str); // "14/02/2019"
  • 역참조와 일치 결과와 마찬가지로 원하는 경우 명명된 그룹과 함께 익명 양식($1 등)을 사용할 수도 있다.

15.4 후방 탐색 지정

  • ES2018은 정규 표현식에 후방 탐색 지정(lookbehind assertion)을 추가했다.
  • 긍정 후방 탐색(positive lookbehind, Y가 일치하지 않는 경우에만 X와 일치)와 부정 후방 탐색(negative lookbehind, Y를 따르지 않는 경우에만 X와 일치하고 Y가 없는 곳은 일치하지 않음)이 있다.
  • 이것은 자바스크립트가 수년 동안 가지고 있던 전방 탐색 지정(lookahead assertion)과 균형을 이룬다.
  • 후방 탐색에서 자바스크립트 정규 표현식의 모든 기능을 사용할 수 있다.

15.4.1 긍정 후방 탐색

  • 긍정 후방 탐색 양식은 (?<=Y)이며, 여기서 Y는 찾을 엔트리다.
  • 예를 들어, 파운드 기호, 영국에 서 사용되는 통화)와 일치하지 않고 파운드 기호 뒤에 오는 숫자를 일치시키려면 긍정 후방 탐색 을 사용하여 반드시 거기에 있어야 한다고 지정할 수 있다.
1const str1 = 'We sold 10 cases for £20 each, and 5 cases for £12.99 each';
2const rex1 = /(?<=£)[\d.]+/g;
3console.log(str1.match(rex1));
4// => ["20", "12.99"]
  • 10과 5파운드 기호가 없기 때문에 어떻게 일치하지 않는지에 주목하자.
  • 일치를 수행하기 위해 개념적으로 엔진은 지정이 아닌 부분 ([\d.]+)에 대한 일치를 찾은 다음 후방 탐색에서 표현식의 각 부분을 가져와서 앞의 텍스트에 대해 테스트하여 후방 탐색 적용한다.
  • 부분별로 일치하며 오른쪽에서 왼쪽으로 이동한다.

15.4.2 부정 후방 탐색

  • 부정 후방 탐색은 (?<IY) 양식을 가진다. 여기서 Y는 존재해서는 안 되는 것이다. 따라서 파운드 기호 뒤의 숫자를 일치시키는 대신 이전 예의 10과 5를 일치시키려면 첫 번째 생각은 아마도 (?<= )(?<! )로 변경하는 것이다.
1const str3 = 'We sold 10 cases for £20 each, and 5 cases for £12.99 each';
2const rex3 = /(?<!£)[\d.]+/g;
3console.log(str3.match(rex3));
4// ['10', '0', '5', '2.99']
  • 20의 0이 일치하는 이유를 잠시 생각해 보자.
  • 20의 0은 바로 앞에 가 없고(바로 앞의 문자는 2임) 12.99의 2.99도 없다(2.99 바로 앞의 문자는 1이다). 따라서 부정 후방 탐색에 숫자와 소수를 추가해야 한다.
  • 긍정 후방 탐색과 마찬가지로 부저어 후방 탐색도 부분별로 오른쪽에서 왼쪽으로 처리된다.
  • 수량자를 상용하지 않는 후방 탐색 내에서 분배 순서는 일반적으로 왼쪽에서 오른쪽이 아니라 오른쪽에서 왼쪽이다. 후방 탐색 내에 하나 이상의 캡처 그룹이 있는 경우 이를 관찰할 수 있다.
1const behind = /(?<=(?<left>\w+)(?<right>\w+))\d$/;
2const behindMatch = "ABCD1".match(behind);
3console.log(behindMatch.groups.left);
4// => "A"
5console.log(behindMatch.groups.right);
6// => "BCD"
  • 후방 탐색의 오른쪽에서 외니쪽 처리 동작에도 불구하고 내부 캡처 그룹의 번호는 동일하게 유지된다. 분배 순서는 오른쪽에서 왼쪽이었지만 그룹 번호는 여전히 왼쪽쪽에서 오른쪽로 할당된다.
1const behindAgain = /(?<=(\w+)(\w+))\d$/;
2const behindMatchAgain = 'ABCD1'.match(behindAgain);
3console.log(behindMatchAgain[1]);
4// => "A"
5console.log(behindMatchAgain[2]);
6// => "BCD"

15.5 유니코드 기능

15.5.1 코드 포인트 이스케이프

  • 이전 유니코드 이스케이프 시퀀스인 \uNNNN은 단일 UTF-16 코드 단위를 정의한다. 그러나 코드 단위는 대리 쌍의 절반에 불과할 수 있다.
  • 예를 들어, 이스케이프 시퀀스스를 사용하여 "웃는 눈에 웃는 얼굴" 이모지(U+1F60A)를 일치시키려면 두 개의 UTF-16 단위(0xD83D0xDEOA)를 나타내는 두 개의 기본 유니코드 이스케이프 시퀀스가 필요하다.
1// Note: This file is encoded in UTF-8. Your system may or may not have a different default encoding.
2
3// These are code UNIT escapes; the next snippet has code POINT escapes
4const rex = /\uD83D\uDE0A/;
5const str = 'Testing: 😊 ';
6console.log(rex.test(str)); // true
  • ES2015 플래그를 사용하는 정규 표현식은 코드 포인트 이스케이프 시퀀스(\u 뒤의 중괄호({) 다음에 16진수로 표시된 코드 포인트 값과 닫는 중괄호(})를 대신 사용할 수 있다.
1// Note: This file is encoded in UTF-8. Your system may or may not have a different default encoding.
2
3const rex = /\u{1F60A}/u;
4const str = 'Testing: 😊 ';
5console.log(rex.test(str)); // true
  • 코드 포인트 이스케이프는 단독으로 작동하지 않는다. 문자 클래스에서 이를 사용하여 코드 포인트 범위와 일치시킬 수 있다. 다음은 "이모티콘” 유니코드 블록(/[\u{1F600}-\u{1F64F}])의 모든 포인트와 일치한다.

15.5.2 유니코드 속성 이스케이프

  • 유니코드 표준은 문자에 숫자 값을 할당할 뿐만 아니라 문자 자체에 대한 엄청난 양의 정보도 제공한다.
  • 예를 들어, 유니코드 표준은 (무엇보다도) 문자 í가 라틴 문자이고 알파벳이며 숫자가 아니며 구두점이 아님을 알려줄 수 있다. 이러한 다양한 것을 유니코드 속성이라고 한다.
  • ES2018부터 u 플래그를 사용하는 정규 표현식은 유니코드 속성으로 문자를 일치시키기 위해 유니코드 속성 이스케이프를 포함할 수 있다.
  • 속성에는 여러 유형이 있다. 자바스크립트의 정규 표현식과 관련된 두 가지는 true 또는 false인 이진 속성과 가능한 값 목록이 있는 열거 속성이다. 예를 들어 \p{Alphabetic} 표현식은 이진 속성 Alphabetic을 사용하여 유니코드 표준에서 알파벳으로 간주 되는 모든 문자와 일치시킨다.
  • 이스케이프는 \p{로 시작하고} 로 끝난다. 속성은 중괄호 내부와 일치한다.
  • \p 는 긍정 유니코드 속성 일치를 위한 것이다. 부정의 경우 소문자 대신 대문자 P를 사용한다.
1// Match alphabetic properties:
2const rex1 = /\p{Alphabetic}/gu;
3const s1 = "Hello, I'm James.";
4console.log(s1.match(rex1));
5// => ["H", "e", "l", "l", "o", "I", "m", "J", "a", "m", "e", "s"]
6
7// Match non-alphabetic properties:
8const rex2 = /\P{Alphabetic}/gu;
9const s2 = "Hello, I'm James.";
10console.log(s2.match(rex2));
11// => [",", " ", "\"", " ", "."]
  • 사용할 수 있는 속성의 종류는 세 가지다.
    • General_Category(별칭: gc): 가장 기본적인 전체 문자 속성, 유니코드 문자를 문자, 구두 사용할 수 있는 속성의 종류는 세 가지다. 점. 기호, 기호, 숫자, 구분 기호 및 기타(다양한 하위 범주 포함)로 분류한다.
    • Script (별칭: sc): 단일 스크립트 범주를 Latin (라틴어), Greek(그리스어), Cyrillic(키릴 자모) 등과 같은 문자에 할당한다.
    • Script_Extensions (별칭: scx): 문자가 발생하는 스크립트를 보다 정확하게 지정하기 위해 스크립트 범주 세트를 문자에 할당한다.
1// Match characters in Greek script:
2const rex3 = /\p{Script_Extensions=Greek}/gu;
3const s3 = 'The greek letters alpha (α), beta (β), and gamma (γ) are used...';
4console.log(s3.match(rex3));
5// => ["α", "β", "γ"]
1// Find punctuation, both listing General_Category explicitly and using
2// the shorthand form leaving it off:
3const rex4a = /\p{General_Category=Punctuation}/gu;
4const rex4b = /\p{Punctuation}/gu;
5const s4 = 'Hello, my name is Pranay. It means "romance" in Hindi.';
6console.log(s4.match(rex4a));
7// => [",", "'", ".", "\"", "\"", "."]
8console.log(s4.match(rex4b));
9// => [",", "'", ".", "\"", "\"", "."]

15.6 과거 습관을 새롭게

15.6.1 부분 문자열을 생성하고 구문 분석할 때 ^를 사용하는 대신 스티키 플래그(y)를 사용하자

  • 문자열을 분할하지 않고 ^ 없이 스티키플래그(y)를 대신 사용하자.

15.6.2 대안을 사용하여 모든 문자(줄 바꿈 포함)를 일치시키는 대신 만능 점 플래그를 사용하자

  • “만능 점 ” 플래그와 . 을 대신 사용하자.

15.6.3 익명 그룹 대신 명명된 캡처 그룹 사용

  • 명명된 캡처 그룹을 사용하고 그룹 객체의 명명된 속성 또는 정규 표현식의 명명된 참조를 사용하자.

15.6.4 다양한 해결 방법 대신 후방 탐색 사용

  • 필요한 경우, 자바스트립트의 강력한 후방 탐색을 대신 사용안다.

15.6.5 정규 표현식에서 대리 쌍 대신 코드 포인트 이스케이프 사용

  • 코드 포인트 이스케이프를 사용하자.
1// 이모지 유니코드 블록과 딩뱃 블록을 모두 일치키려면..
2// ASIS
3const rex = /(?:\uD83D[\uDE00-\uDE4F]|[\u2700-\u27BF])/;
4
5// TOBE
6const rex = /[\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}]/u;

15.6.6 대안 대신 유니코드 패턴 사용

  • 일치시킬 유니코드 범위를 선택하는 유지 관리가 어려운 방식 대신 유니코드 속성 이스케이프를 사용하자.