문제 설명
JavaScript의 분할 메소드를 사용하여 문자열을 분할할 때, 특히 정규 표현식을 구분 기호로 사용할 때 일부 빈 문자열 ""이 나타납니다.
관련 질문
문자열을 그룹화할 때 Javascript 정규 표현식이 빈 문자열 그룹을 생성합니까?
위 질문에서 피험자가 문자열을 분할하기 위해 정규식을 사용했을 때 여러 개의 빈 문자열 ""이 생성되었습니다. 코드는 다음과 같습니다.
그렇다면 이렇게 빈 문자열이 있는 이유는 무엇일까요?
문제 분석
구글에서 검색해 보니 관련 결과도 많지 않고, 있다고 해도 자세한 설명이 많지 않은 것 같아서 간략하게 소개를 한 뒤 ECMAScript 스펙에 대한 링크를 걸어 드렸습니다. 진짜 이유를 알고 싶으면 일단 짚고 넘어가서 규정을 읽어봐야 할 것 같습니다.
관련기준
그럼 다음은 국제 관례에 따라 ECMAScript의 표준 타운홀부터 시작하겠습니다.
이 장에서는 분할 방법의 실행 단계를 자세히 소개합니다. 관심이 있으시면 단계별로 주의 깊게 읽으십시오. 여기서는 부적절할 경우 빈 문자열 생성과 관련된 단계만 설명하겠습니다. 모두가 제안을 환영합니다.
관련 단계
몇 가지 단계 추출:
전체 과정에서 가장 중요한 단계는 13단계의 주기이며, 이 주기가 수행하는 주요 작업은 다음과 같습니다.
•p와 q의 값을 정의합니다. 각 루프의 시작 부분에서 p와 q의 값이 동일합니다(이 단계는 루프 외부에 있음).
• 문자열을 분할하려면 SplitMatch(S, q, R) 메서드를 호출하세요.
• 반환된 다양한 결과에 따라 다양한 분기가 실행되며 주요 분기는 ⅲ 분기입니다.
•분기 ⅲ는 8개의 작은 단계로 나누어 반환된 결과를 미리 정의된 배열 A
에 채웁니다.
•이 8개의 작은 단계에서 1단계의 기능은 원래 문자열의 하위 문자열을 반환하는 것입니다. 시작 위치는 p(포함)이고 끝 위치는 q(포함되지 않음)입니다. 참고: 이 단계에서 빈 문자열은 다음과 같습니다. 아래에서 쉽게 참조할 수 있도록 잘린 문자열로 표시했습니다.
• 이전 단계의 하위 문자열을 배열 A에 추가
•다음 몇 단계는 관련 변수를 업데이트하고 다음 주기를 계속하는 것입니다. (7단계의 기능은 정규식의 캡처 그룹을 배열 A에 저장하는 것이며 빈 문자열 생성과는 아무런 관련이 없습니다)
SplitMatch(S, q, R)
다음으로 SplitMatch(S, q, R) 메서드의 기능을 이해해야 합니다. 이 방법은 분할 사양에서 더 자세히 언급됩니다. 주로 하는 일은 구분 기호 유형에 따라 해당 작업을 수행하는 것입니다.
• 구분자가 문자열인 경우 일치 여부를 판단하여 실패를 반환하며, MatchResult 형태의 결과를 성공적으로 반환한다.
경기결과
위 단계에서는 MatchResult 유형의 또 다른 변수를 소개합니다. 문서를 확인하여 이러한 유형의 변수에는 endIndex와 캡처라는 두 가지 속성이 있음을 발견했습니다. endIndex의 값은 문자열 일치 위치에 1을 더한 것입니다. 캡처는 구분 기호가 정규 표현식인 경우 요소로 이해될 수 있습니다. 그 안에는 그룹에서 캡처한 값이 있습니다. 구분 기호가 문자열이면 빈 배열입니다.
다음
위의 단계를 보면 문자열을 가로채는 단계에서 분할된 문자열이 생성되는 것을 볼 수 있습니다(정규식의 그룹 캡처는 제외). 그 기능은 지정된 시작(포함)과 끝 위치(포함되지 않음) 사이의 문자열을 가로채는 것입니다. 그러면 언제 ""를 반환합니까? 시작 위치와 끝 위치의 값이 동일한 특별한 경우가 있습니다. 이는 사양에서 문자열을 가로채기 위한 표준 단계를 제공하지 않기 때문에 단지 추측일 뿐입니다.
여기까지 왔는데, 한발 더 나아가보면 어떨까요?
그래서 구체적인 구현 방법을 찾을 수 있는지 알아보기 위해 V8 소스 코드를 검색해 보았습니다. 관련 코드, 소스 코드 링크를 찾았습니다
다음은 그중 하나를 발췌한 것입니다.
if (한계 === 0) return [];
// ECMA-262에서는 구분 기호가 정의되지 않은 경우 결과가 다음과 같아야 한다고 말합니다.
// 전체 문자열을 포함하는 크기 1의 배열입니다.
(IS_UNDEFINED(구분자))인 경우 [제목]을 반환합니다.
var 분리_길이 = 분리_문자열.길이;
//구분자는 빈 문자열이고 문자 배열이 직접 반환됩니다.
If (separator_length === 0) return %StringToArray(subject,limit);
var 결과 = %StringSplit(제목, 구분 기호_문자열, 제한);
결과 반환;
}
if (한계 === 0) return [];
// 구분자가 정규식인 경우 StringSplitOnRegExp를 호출합니다
Return StringSplitOnRegExp(제목, 구분 기호, 제한, 길이);
}
//여기서 일부 코드는 생략하세요
배열을 채울 때 문자열을 가로채기 위해 %_SubString 메서드가 호출되는 코드를 발견했습니다. 안타깝게도 해당 정의를 찾지 못했다면 알려주세요. 그런데 JavaScript의 substring 메서드에 해당하는 StringSubstring 메서드가 %_SubString 메서드를 호출하여 결과를 반환한다는 사실을 발견했습니다. 그런 다음 'abc'.substring(1,1)이 ""를 반환하면 시작 위치와 끝 위치가 동일할 때 %_SubString 메서드가 ""를 반환한다는 의미입니다. 시도한 후에 결과를 알 수 있습니다.
그렇다면 언제 시작 위치가 종료 위치와 같게 될까요(예: q === p)? 위의 단계에 따라 단계별로 분석한 결과 다음과 같은 사실을 발견했습니다.
•원래 문자열 S가 구분 기호와 한 번 일치한 후 문자열 S의 다음 위치도 구분 기호와 일치합니다. 예: 'abbbc'.split('b'), 'abbbc'.split(/(b){1}/)
•또 다른 상황은 문자열 시작 부분의 하나 또는 여러 문자가 구분 기호와 일치하는 경우입니다. 예: 'abc'.split('a'), 'abc'.split(/ab/)
•문자열 끝의 하나 또는 여러 개의 문자열이 구분 기호와 일치하는 경우도 있으며 관련 단계는 14 단계입니다.
예: 'abc'.split('c'), 'abc'.split(/bc/)
또한 정규 표현식을 구분 기호로 사용하는 경우 반환된 결과에 정의되지 않음이 나타날 수 있습니다.
예: 'abc'.split(/(d)*/)
다시 돌아가서 처음의 예를 살펴보세요. 위 조건을 충족합니까?
여담
ECMAScript 표준 사양을 이렇게 주의 깊게 읽어본 것은 처음입니다. 읽는 과정은 참으로 고통스럽지만 이해하고 나면 매우 행복합니다. 이 질문을 제기하고 후속 조치를 취해 주신 질문자에게도 감사드립니다.
그런데 정규식을 구분 기호로 사용하면 전역 수정자 g가 무시되므로 추가적인 이점이 있습니다.