2023. 7. 13. 15:30

예전부터 정규식 개체(Regexp)를 사용할 때 값이 제대로 일치하다 말다 한다는 생각을 많이 했습니다.

그래서 가급적 정규식 개체를 사용하지 않았는데......

의외로 원인은 쉬운 곳에 있었습니다.

 

 

1. 문제의 발견

아래와 같이 정규식 개체를 만들고 

/** 정규식 개체 */
reg = /\{\{[a-zA-Z0-9]+:[a-zA-Z0-9]+\}\}|\{\{[a-zA-Z0-9]+\}\}/g
/** 테스트 문자열 */
testString = "{{test001}}, {{test001:test1}}, {{test001:a}}, {{aa}}";

 

아래와 같이 정규식 개체를 사용하는 함수를 만들어 줍니다.

private F1(): void
{
    console.log("F1 : " + this.reg.exec(this.testString));
}

private F2(): void
{
    console.log("F2 : " + this.reg.exec(this.testString));
}

private F3(): void
{
    console.log("F3 : " + this.reg.exec(this.testString));
}

 

이제 이 함수를 각각 호출해 봅시다.

for (let i = 0; i < 1; ++i)
{
    console.log("----- -----");
    this.F1();
    this.F2();
    this.F3();
}

엨?

 

"다 같은 값이 나와야 하는 게 아닌가?"

라는 생각을 한다면 'Regexp.exec'를 잘못 이해하고 있는 겁니다 ㅎㅎㅎㅎ

(글로벌 플래그(/g)가 없을 때만 같은 값이 나옵니다.)

 

 

2. 호출 순서가 중요하다.

'Regexp.exec'는 글로벌 플래그(/g)로 선언되어 있으면 호출될 때마다 일치하는 것을 하나씩 리턴합니다.

 

문제는 위와 같은 예제는 호출 시점이 한눈에 보이니 큰문제가 안 되는데

프로젝트가 커지면 누가 먼저 호출하는지 알 방법이 없습니다.

그러다 보니 '범위 안에서는 자동으로 처음부터 검색하는 게 아닌가?'라는 착각을 하게 됩니다.

 

무조건 자신이 맨 처음 호출하게 하고 싶으면 라스트 인덱스(lastindex)를 0으로 초기화해서 사용해야 합니다.

다시 처음부터 시작한다.

 

 

3. 우리가 하고 있던 착각

결국 우리는 정규식 개체에 대해 착각하고 있던 겁니다.

 

아래 코드를 실행해 봅시다.

for (let i = 0; i < 1; ++i)
{
    console.log("----- -----");

    this.F1();
    console.log("F1 - lastIndex : " + this.reg.lastIndex);
    this.F2();
    console.log("F2 - lastIndex : " + this.reg.lastIndex);
    this.F3();
    console.log("F3 - lastIndex : " + this.reg.lastIndex);

    this.F1();
    console.log("F1 - lastIndex : " + this.reg.lastIndex);
    this.F2();
    console.log("F2 - lastIndex : " + this.reg.lastIndex);
    this.F3();
    console.log("F3 - lastIndex : " + this.reg.lastIndex);
}

이 결과로 두가지의 조심해야 할 것이 보이는데....

 

1) 'Regexp.exec'는 일치하는 결과를 하나씩 리턴하고 라스트 인덱스가 저장된다.

라스트 인덱스는 일치하는 값이 시작 위치가 저장됩니다.

 

2) 일치 값이 맨 마지막에 있다면 다음 검색은 무조건 'null' 나온다.

라스트 인덱스 뒤로 일치하는 값이 없다면 'null'이 나오므로 맨 마지막 일치 값 다음 값은 무조건 'null'입니다.

일치하는 값이 1개 이상 있다고 'null'이 안 나오겠지하고 예외 처리 안 하면 에러 난다는 의미입니다.

 

 

마무리

테스트 프로젝트 : github - dang-gun/HtmlJavascriptSamples/JavascriptRegexObject/

 

참고 :

stackoverflow - RegExp.exec() returns NULL sporadically - Frode님 답변

MDN - RegExp.prototype.exec()

 

이렇게 중요한 내용이 국내외 막론하고 정규식 개체를 설명할 때 언급하는 경우를 거의 못 봤습니다.

그나마 문서에는 뭐에 쓰는 건지 정도의 언급만......