Development/Javascript

[Typescript] RegExp의 함수를 사용할 때 주의할 점

nabina 2020. 5. 6. 19:00
728x90

서론

Typescript나 Javascript에서 RegExp 객체를 변수화한 뒤에 test()나 exec() 함수를 사용할 때는 특별히 주의할 점이 있다. 바로 lastIndex. 정규표현식을 사용할 때 flag로 넣을 수 있는 옵션 중에 g가 있는데 이것은 전체 찾기를 하겠다는 옵션이다. 이걸 넣어서 사용하면 내부적으로 lastIndex를 사용하게 되는데 lastIndex 속성은 함수를 실행할 때마다 값을 기억하고 다시 시작할 때 그 위치에서 검색을 시작한다. 그러니까 대상자 하나를 가지고 검색할 때는 괜찮을 수 있으나, 단순 검색으로 여러 대상을 가지고 검색을 할 때는 100% 문제가 된다.

원인

 

const regex: RegExp = /such/ig;
const testValue1: string = 'You are such a great!!';
const testValue2: string = 'It was such a nice day';
// DESC 변수화한 객체로 test 실행
console.log( regex.test( testValue1 ) );
console.log( regex.test( testValue2 ) );
/*
true
false
*/

 

위의 코드를 보면 결과값이 true, true, 가 나오는 것으로 기대하지만 실제로는 true, false 로 나온다. 왜 이런 차이가 발생하는 것일까? 서론에서도 말한 것과 같이 Global Flag가 설정되면 전체 단위로 찾기 때문에 한 번만 찾는 게 아니게 된다. 따라서 여러 개를 찾아야 하기 때문에 현재까지 찾은 index를 내부적으로 가지고 있는데 그게 바로 lastIndex 속성이다. 위와 같은 소스에서 중간 중간에 lastIndex를 찍어보자.

 

const regex: RegExp = /such/ig;
const testValue1: string = 'You are such a great!!';
const testValue2: string = 'It was such a nice day';
// DESC 변수화한 객체로 test 실행
console.log( regex.lastIndex );
console.log( regex.test( testValue1 ) );
console.log( regex.lastIndex );
console.log( regex.test( testValue2 ) );
console.log( regex.lastIndex );
/*
0
true
12
false
0
*/

 

결과값을 보면 알겠지만 0, true, 12, false, 0 이렇게 나온다. 중간에 12라는 값은 첫 번째에 검색했던 You are such a great에서 such의 마지막 Index 값이다. 그러니까 h의 index이다. 그러고 나서 두 번째 검색할 때는 lastIndex가 12가 된 상태에서 시작하기 때문에 12번째 자릿수 뒤로부터 검색하니 testValue2의 test 함수 리턴값은 false가 나온 것이다. 만약에 testValue2의 such가 12번째 이후에 있었다면 true가 나와서 잘못 사용했지만 버그는 발견되지 않는 아주 나쁜 상황이 될 수도 있다. 따라서 다음과 같이 사용해야 한다.

해결

해결 방법에는 당연히 여러가지가 있겠지만 크게 분류하자면 두 가지정도로 나누어질 듯 싶다. (I think), (제가 몰랐던 아름답고 멋진 코드는 언제나 환영입니다~~)

해결 1. Regex 리터럴 사용

 

const regex: RegExp = /such/ig;
const testValue1: string = 'You are such a great!!';
const testValue2: string = 'It was such a nice day';
// DESC 해결 방법 1
console.log( /such/ig.test( testValue1 ) );
console.log( /such/ig.test( testValue2 ) );
/*
true
true
*/

 

변수화(상수화 포함) 하지 않고 리터럴을 사용하여 test함수를 사용하는 방법이다. 이 방법의 단점은 상수화하지 못 해 반복적인 코드일 때 단일화할 수 없다는 것과 선언적 코드가 로직 중간에 들어가게 되는 단점이 발생한다. 그래서 나는 주로 두 번째 해결 방법을 사용한다.

해결2. 새롭게 Regex 객체를 생성하여 사용

 

const regex: RegExp = /such/ig;
const testValue1: string = 'You are such a great!!';
const testValue2: string = 'It was such a nice day';
// DESC 해결 방법 2
console.log( ( new RegExp( regex ) ).test( testValue1 ) );
console.log( ( new RegExp( regex ) ).test( testValue2 ) );
/*
true
true
*/

 

이 방법도 맘에 들지 않는다는 단점이 있지만 (생성하고 또 생성한다는 게 맘에 안들어요ㅜㅜ) 그나마 깔끔한 코드인 듯 싶다. Constructor Parameter로는 RegExp 객체도 들어가고 String도 들어가기 때문에 구분없이 사용해도 된다.

해결3. lastIndex 강제 초기화

DEFAULT_REGEXP.lastIndex = 0;
const [ s, p1, p2, p3, p4, p5, offset, others ] = DEFAULT_REGEXP.exec( inputStr );

Array에 index를 0으로 주입하면 전체 삭제가 되는 효과를 얻을 수 있는 것처럼 lastIndex를 0으로 바꾸면 위의 문제를 해결할 수 있다.

 

참고

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/%EC%A0%95%EA%B7%9C%EC%8B%9D

 

정규 표현식

정규 표현식은 문자열에 나타는 특정 문자 조합과 대응시키기 위해 사용되는 패턴입니다. 자바스크립트에서, 정규 표현식 또한 객체입니다.  이 패턴들은 RegExp의 exec 메소드와 test 메소드  ,그리고 String의  match메소드 , replace메소드 , search메소드 ,  split 메소드와 함께 쓰입니다 . 이 장에서는 자바스크립트의 정규식에 대하여 설명합니다.

developer.mozilla.org

 

반응형