본문 바로가기

Development/Javascript

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

서론

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

원인

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

결과값을 보면 알겠지만 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 리터럴 사용

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

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

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

정리

코드는 최대한 간단하게 작성하려해서 함수화 하지 않았다. 그래서 실제로 사용할 때는 함수화 해서 사용하는 것이 좋을 것 같고, 해결 방법으로 lastIndex를 초기화 시키는 방법도 있을 것 같긴한데 이것도 그닥 맘에 들진 않고, (비슷한 예로 Array.length = 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