전역 플래그가있는 RegExp가 왜 잘못된 결과를 제공합니까?


277

전역 플래그와 대소 문자를 구분하지 않는 플래그를 사용할 때이 정규식의 문제점은 무엇입니까? 쿼리는 사용자 생성 입력입니다. 결과는 [true, true] 여야합니다.

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
result.push(re.test('Foo Bar'));
// result will be [true, false]

var reg = /^a$/g;
for(i = 0; i++ < 10;)
   console.log(reg.test("a"));


54
JavaScript에서 RegExp의 많은 함정 중 하나에 오신 것을 환영합니다. 그것은 내가 만난 정규 표현식 처리에 대한 최악의 인터페이스 중 하나이며 이상한 부작용과 모호한주의로 가득합니다. 일반적으로 정규 표현식으로 수행하려는 대부분의 일반적인 작업은 철자가 어렵습니다.
bobince

XRegExp는 좋은 대안으로 보입니다. xregexp.com
에 약

여기에서도 답변을 참조하십시오 : stackoverflow.com/questions/604860/…
Prestaul

해결할 수있는 한 가지 해결책은 정규 표현식 리터럴을에 저장하는 대신 직접 사용하는 것입니다 re.
thdoan

답변:


350

RegExp객체의 추적 lastIndex일치가 발생한 위치 때문에 다음 경기에가보세요 0 대신, 마지막으로 사용 된 인덱스에서 시작됩니다 :

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));

alert(re.lastIndex);

result.push(re.test('Foo Bar'));

lastIndex모든 테스트 후에 수동으로 0 으로 재설정하지 않으려면 g플래그를 제거하십시오 .

스펙이 지시하는 알고리즘은 다음과 같습니다 (섹션 15.10.6.2).

RegExp.prototype.exec (문자열)

정규식에 대해 string의 정규식 일치를 수행하고 일치 결과를 포함하는 Array 객체를 반환하거나 문자열이 일치하지 않으면 null을 반환합니다. ToString (string) 문자열은 다음과 같이 정규식 패턴의 발생을 검색합니다.

  1. S를 ToString (string)의 값으로 설정하십시오.
  2. length를 S의 길이라고하자.
  3. lastIndex를 lastIndex 속성 값으로 설정하십시오.
  4. 내가 ToInteger (lastIndex)의 값이라고합시다.
  5. 전역 속성이 false이면 i = 0으로 설정하십시오.
  6. I <0 또는 I> length이면 lastIndex를 0으로 설정하고 null을 반환합니다.
  7. [[Match]]를 호출하여 인수 S와 i를 지정하십시오. [[Match]]가 실패를 반환하면 8 단계로 이동하십시오. 그렇지 않으면 r을 State 결과로 설정하고 10 단계로 이동하십시오.
  8. i = i + 1이라고하자.
  9. 6 단계로 이동하십시오.
  10. e를 r의 endIndex 값으로하자.
  11. 전역 속성이 true이면 lastIndex를 e로 설정하십시오.
  12. n을 r의 캡처 배열의 길이라고하자. (15.10.2.1의 NCapturingParens와 같은 값입니다.)
  13. 다음 속성을 가진 새 배열을 반환하십시오.
    • 인덱스 속성은 전체 문자열 S 내에서 일치하는 하위 문자열의 위치로 설정됩니다.
    • 입력 특성이 S로 설정되어 있습니다.
    • 길이 속성은 n + 1로 설정되어 있습니다.
    • 0 속성은 일치하는 부분 문자열로 설정됩니다 (즉, 오프셋 i와 오프셋 e 사이의 S 부분).
    • I> 0 및 I ≤ n 인 각 정수 i에 대해 ToString (i)이라는 특성을 r의 캡처 배열의 i 번째 요소로 설정하십시오.

83
이것은 갤럭시 API 디자인에 대한 히치하이커 가이드와 같습니다. "당신이
빠뜨린 함정

5
Firefox의 끈적 끈적한 깃발은 당신이 암시하는 것을 전혀하지 않습니다. 오히려 정규 표현식의 시작 부분에 ^가있는 것처럼 작동합니다.이 ^는 문자열의 시작 위치가 아닌 현재 문자열 위치 (lastIndex) 와 일치 합니다. 정규식이 "lastwhere after lastIndex"대신 "here here"와 일치하는지 효과적으로 테스트하고 있습니다. 제공 한 링크를 참조하십시오!
Doin

1
이 답변의 첫 진술은 정확하지 않습니다. 아무것도 말하지 않는 스펙의 3 단계를 강조 표시했습니다. 실제 영향은 lastIndex5, 6, 11 단계에 있습니다. 귀하의 개시 진술은 GLOBAL FLAG가 설정되어있는 경우에만 해당됩니다.
Prestaul

@Prestaul 예, 글로벌 플래그를 언급하지 않은 것이 맞습니다. 질문의 틀을 잡는 방식으로 인해 암시 적이었을 것입니다. 답변을 편집하거나 삭제하고 답변에 연결하십시오. 또한 당신이 나보다 낫다는 것을 확신시켜 드리겠습니다. 즐겨!
Ionuț G. Stan

@ IonuțG.Stan, 내 이전 의견이 공격적으로 보이면 미안합니다. 이 시점에서 편집 할 수는 없지만, 내 의견의 핵심에주의를 기울이려고 외치려고하지 않았습니다. 내 잘못이야!
Prestaul

72

단일 RegExp객체를 사용하고 여러 번 실행 중입니다. 각 연속적인 실행에서 마지막 일치 색인부터 계속됩니다.

각 실행 전에 처음부터 시작하려면 정규식을 "재설정"해야합니다.

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
// result is now [true, true]

매번 새로운 RegExp 객체를 생성하는 것이 더 읽기 쉬울 수 있다고 말했지만 (RegExp가 캐싱되므로 오버 헤드는 최소화됩니다) :

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));

1
또는 단순히 g플래그를 사용하지 마십시오 .
melpomene

36

RegExp.prototype.test정규식의 lastIndex속성을 업데이트하여 각 테스트가 마지막 테스트가 중지 된 위치에서 시작되도록합니다. 속성을 String.prototype.match업데이트하지 않기 때문에 사용하는 것이 좋습니다 lastIndex.

!!'Foo Bar'.match(re); // -> true
!!'Foo Bar'.match(re); // -> true

참고 : !!이를 부울로 변환 한 다음 부울을 뒤집어 결과를 반영합니다.

또는 lastIndex속성을 재설정 할 수도 있습니다.

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));

11

전역 g플래그를 제거 하면 문제가 해결됩니다.

var re = new RegExp(query, 'gi');

해야한다

var re = new RegExp(query, 'i');

0

g 플래그 regex를 사용하면 마지막 일치 항목을 추적하기 때문에 re.lastIndex = 0을 설정해야하므로 테스트는 동일한 문자열을 테스트하지 않으므로 re.lastIndex = 0을 수행해야합니다.

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
re.lastIndex=0;
result.push(re.test('Foo Bar'));

console.log(result)



-1

나는 기능을했다 :

function parseDevName(name) {
  var re = /^([^-]+)-([^-]+)-([^-]+)$/g;
  var match = re.exec(name);
  return match.slice(1,4);
}

var rv = parseDevName("BR-H-01");
rv = parseDevName("BR-H-01");

첫 번째 전화가 작동합니다. 두 번째 전화는 그렇지 않습니다. slice작업은 널 (null) 값에 대해 불평. 나는 이것이 때문이라고 가정합니다 re.lastIndex. RegExp함수가 호출 될 때마다 새로운 함수가 할당되고 함수를 여러 번 호출 할 때 공유되지 않기 때문에 이상 합니다.

내가 그것을 변경했을 때 :

var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g');

그런 다음 lastIndex홀드 오버 효과를 얻지 못합니다 . 예상대로 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.