<input>
에 입력된 글자의 byte를 계산하기 위해 for loop
안에서 RegExp.test()
메서드를 호출했을때 예상과 다른 결과를 가져왔다!🧐
문제의 코드는 다음과 같다.
calculateBytes (value) {
const string = value;
let byte = 0;
const REGEX_1BYTE = new RegExp(`[${this.1BYTE_REGEX_STR}]`, 'g'); // 영문, 숫자
const REGEX_2BYTE = new RegExp(`[${this.2BYTE_REGEX_STR}]`, 'g'); // 한글
for (let i = 0; i < value.length; i += 1) {
byte += REGEX_1BYTE.test(value[i]) * 1 + REGEX_2BYTE.test(value[i]) * 2;
}
return byte;
},
calculateBytes('대기10'); // 3
calculateBytes('대1기0'); // 6
calculateBytes('대기10')
와 calculateBytes('대1기0')
의 결과값이 다르게 나온다고? 뭐가 문제지? RegExp.test()
를 잘못 쓰고 있는건가?
Regex.prototype.test()
는 특정 글자에 정규 표현식에 해당하는 글자가 있는지 여부를 Boolean
값으로 반환해준다.
예시
const regex = new RegExp('[0-9]'); // 0-9를 허용하는 정규 표현식
regex.test('1000'); // true
regex.test('한국'); // false
뭐가 문제인지 MDN에서 RegExp.prototype.test()를 다시 찾아봤고, 개념 설명 아래쪽에 예시가 있어 다시 자세히 보니 오잉? 이게 모지? 싶은게 있었다.
const str = 'table football';
const regex = new RegExp('foo*');
const globalRegex = new RegExp('foo*', 'g');
console.log(regex.test(str)); // 1
// Expected output: true
console.log(globalRegex.lastIndex);
// Expected output: 0
console.log(globalRegex.test(str)); // 2
// Expected output: true
console.log(globalRegex.lastIndex);
// Expected output: 9
console.log(globalRegex.test(str)); // 3
// Expected output: false
여기서 보면, 첫번째 두번째 globalRegex.test()
를 호출시 결과는 true
다. 하지만 세번째 호출시 false
가 나왔다.
regex
변수와 globalRegex
변수의 차이점은 Regex
인스턴스를 생성시 'g'
flag 여부이다.
그럼 'g'
flag가 있으면 globalRegex.lastIndex
( console.log(globalRegex.test(str))
사이의 코드)가 달라져서 예상과 다른 결과가 나오는건가?
아래 조금 더 내려보니 이런 설명이 있었다.
exec()처럼, test()도 전역 탐색 플래그를 제공한 정규 표현식에서 여러 번 호출하면 이전 일치 이후부터 탐색합니다. exec()와 test()를 혼용해 사용하는 경우에도 해당됩니다.
즉 'g'
flag가 있는 정규 표현식에 test()
를 호출할 때, 일치하는 글자를 찾으면 그 글자의 인덱스를 lastIndex에 저장해 다음 test()
호출시 해당 인덱스부터 탐색을 시작하는 것이다.
참고
exac()도 동일하게'g'
를 사용하면lastIndex
를 저장한다.
문제가 된 코드를 다시 살펴보자
calculateBytes (value) {
const string = value;
let byte = 0;
const REGEX_1BYTE = new RegExp(`[${this.1BYTE_REGEX_STR}]`, 'g'); // 영문, 숫자
const REGEX_2BYTE = new RegExp(`[${this.2BYTE_REGEX_STR}]`, 'g'); // 한글
for (let i = 0; i < value.length; i += 1) {
byte += REGEX_1BYTE.test(value[i]) * 1 + REGEX_2BYTE.test(value[i]) * 2;
}
return byte;
},
calculateBytes('대기10'); // 3
calculateBytes('대1기0'); // 6
'대기10' 글자를 for loop
돌리면 발생하는 일 (숫자는 for loop
횟수를 의미한다.)
1. '대'
REGEX_1BYTE.lastIndx
와 REGEX_2BYTE.lastIndx
모두 0이고, 글자를 0번째부터 탐색한다.
'대'가 REGEX_2BYTE
에 해당되어 byte = 0 + 2 = 2가된다.
동시에, REGEX_2BYTE.lastIndex
에 1이 저장된다. (REGEX_1BYTE.lastIndx
는 여전히 0)
2. '기'
REGEX_1BYTE.lastIndx
는 0이고, 글자의 0번째부터 탐색, REGEX_2BYTE.lastIndx
는 1이고 글자의 첫번째 부터 탐색한다.
'기'는 글자수가 한개이므로 인덱스 1이 존재하지 않는다.
따라서REGEX_1BYTE
는 해당하지 않아서 false
, REGEX_2BYTE
는 탐색할 글자가 없어서 false
를 반환한다.
그리고 각 정규 표현식의 lastIndex
는 0이 저장된다. (byte는 2)
3. '1'
REGEX_1BYTE.lastIndx
와 REGEX_2BYTE.lastIndx
모두 0이고, 글자를 0번째부터 탐색한다.
'1'이 REGEX_1BYTE
에 해당되어 byte = 2 + 1 = 3가된다.
마찬가지로, REGEX_1BYTE.lastIndex
에 1이 저장된다. (REGEX_2BYTE.lastIndx
는 0)
4. '0'
REGEX_1BYTE.lastIndx
는 1이고 글자의 첫번째 부터탐색, REGEX_2BYTE.lastIndx
는 0이고, 글자의 0번째부터 탐색한다.
'0'는 글자수가 한개이므로 인덱스 1이 존재하지 않는다.
따라서REGEX_1BYTE
는 탐색할 글자가 없어서 false
, REGEX_2BYTE
는 해당하지 않아서 false
를 반환한다.
그리고 각 정규 표현식의 lastIndex
는 0이 저장된다. (byte는 3)
이런 이유로 calculateBytes('대기10')
와 calculateBytes('대1기0')
의 결과값이 다르게 나온것이다.
즉, RegExp의 lastIndex를 초기화 시켜주는 방법을 찾으면 된다!
REGEX_1BYTE
와, REGEX_2BYTE
를 for loop
안에서 선언해주면 각 for loop
마다 새로운 변수로 선언되기 때문에 lastIndex
가 초기화 된다.
결과
calculateBytes (value) {
const string = value;
let byte = 0;
for (let i = 0; i < value.length; i += 1) {
/* 변경된 코드 시작 */
const REGEX_1BYTE = new RegExp(`[${this.1BYTE_REGEX_STR}]`, 'g'); // 영문, 숫자
const REGEX_2BYTE = new RegExp(`[${this.2BYTE_REGEX_STR}]`, 'g'); // 한글
/* 변경된 코드 끝 */
byte += REGEX_1BYTE.test(value[i]) * 1 + REGEX_2BYTE.test(value[i]) * 2;
}
return byte;
},
calculateBytes('대기10'); // 6
calculateBytes('대1기0'); // 6
calculateBytes('대기10')
와 calculateBytes('대1기0')
가 같은 결과를 얻는다!