정규 표현식 심화 — Named Group, Lookbehind, dotAll, Unicode
정규 표현식 기초를 넘어서면 Named Group, Lookbehind Assertion, dotAll, Unicode 플래그 같은 강력한 기능이 있습니다. ES2018 이후 추가된 이 기능들을 알면 더 읽기 좋고 강력한 패턴을 작성할 수 있습니다.
Named Capturing Group
숫자 인덱스 대신 이름으로 캡처 그룹에 접근합니다.
// 기존 방식 — 인덱스 기반 (읽기 어려움)
const dateRegex = /(\d{4})-(\d{2})-(\d{2})/;
const match = "2026-03-28".match(dateRegex);
console.log(match[1]); // "2026" — 뭔지 기억해야 함
// Named Group — 이름 기반 (읽기 쉬움)
const namedRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const namedMatch = "2026-03-28".match(namedRegex);
console.log(namedMatch.groups.year); // "2026"
console.log(namedMatch.groups.month); // "03"
console.log(namedMatch.groups.day); // "28"
// 구조 분해와 함께
const { groups: { year, month, day } } = "2026-03-28".match(namedRegex);
replace에서 Named Group 참조
// $<name>으로 참조
"2026-03-28".replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<year>년 $<month>월 $<day>일"
);
// "2026년 03월 28일"
// 함수와 함께
"2026-03-28".replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
(...args) => {
const { year, month, day } = args.at(-1); // 마지막 인자가 groups
return `${year}/${month}/${day}`;
}
);
Lookbehind Assertion (ES2018)
// Lookahead (앞을 봄) — 이전부터 있었음
/\d+(?=원)/.exec("가격: 5000원"); // ["5000"] — "원" 앞의 숫자
/\d+(?!원)/.exec("5000달러"); // ["5000"] — "원"이 아닌 것 앞의 숫자
// Lookbehind (뒤를 봄) — ES2018 추가
/(?<=\$)\d+/.exec("가격: $100"); // ["100"] — "$" 뒤의 숫자
/(?<=₩)\d+/.exec("₩5000"); // ["5000"] — "₩" 뒤의 숫자
// Negative Lookbehind
/(?<!\$)\d+/.exec("가격: 100원"); // ["100"] — "$"가 아닌 것 뒤의 숫자
실전 — 비밀번호 마스킹
// 앞 4자리와 뒤 4자리만 남기고 마스킹
function maskMiddle(str) {
return str.replace(/(?<=.{4}).(?=.{4})/g, "*");
}
console.log(maskMiddle("1234567890")); // "1234**7890"
dotAll 플래그 (s)
// 기본: .은 줄바꿈을 매칭하지 않음
/hello.world/.test("hello\nworld"); // false
// dotAll: .이 줄바꿈도 매칭
/hello.world/s.test("hello\nworld"); // true
// 실전: 여러 줄 HTML 매칭
const html = `<div>
<p>내용</p>
</div>`;
html.match(/<div>.*<\/div>/s); // 매칭됨
html.match(/<div>.*<\/div>/); // 매칭 안 됨
Unicode 플래그 (u)와 유니코드 속성
// u 플래그 — 유니코드 모드
/\u{1F600}/u.test("😀"); // true
/\u{1F600}/.test("😀"); // false (u 없으면 리터럴로 해석)
// Unicode Property Escapes (\p{})
/\p{Emoji}/u.test("😀"); // true — 이모지
/\p{Script=Hangul}/u.test("가"); // true — 한글
/\p{Script=Han}/u.test("漢"); // true — 한자
/\p{Letter}/u.test("a"); // true — 모든 문자
// 실전: 한글만 매칭
const koreanOnly = /^\p{Script=Hangul}+$/u;
koreanOnly.test("안녕하세요"); // true
koreanOnly.test("Hello"); // false
koreanOnly.test("안녕123"); // false
v 플래그 (ES2024) — 유니코드 집합
// v 플래그는 u의 상위 호환
// 집합 연산 지원
/[\p{Script=Greek}&&\p{Letter}]/v; // 그리스 문자 중 글자만
/[\p{Decimal_Number}--[0-9]]/v; // 10진 숫자 중 ASCII 제외
/[[\p{Letter}]&&[\p{ASCII}]]/v; // ASCII 문자만
// 문자열 속성
/\p{Basic_Emoji}/v.test("🫠"); // true
matchAll — 모든 매칭 반복
const text = "오늘 날짜: 2026-03-28, 어제: 2026-03-27";
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;
for (const match of text.matchAll(regex)) {
const { year, month, day } = match.groups;
console.log(`${year}년 ${month}월 ${day}일 (위치: ${match.index})`);
}
// 2026년 03월 28일 (위치: 6)
// 2026년 03월 27일 (위치: 22)
Named Backreference
// 같은 문자가 반복되는 패턴
/(?<char>.)\k<char>/.test("aa"); // true
/(?<char>.)\k<char>/.test("ab"); // false
// HTML 태그 매칭 (열기/닫기 태그 일치)
/(?<tag>\w+)>.*?<\/\k<tag>/.test("div>content</div"); // true
실전 패턴 모음
// IP 주소 추출
const ipRegex = /(?<octet1>\d{1,3})\.(?<octet2>\d{1,3})\.(?<octet3>\d{1,3})\.(?<octet4>\d{1,3})/;
// URL에서 도메인 추출
const urlRegex = /(?<=https?:\/\/)(?<domain>[^\/]+)/;
const { groups: { domain } } = "https://www.example.com/path".match(urlRegex);
// domain: "www.example.com"
// 가격 추출 (통화 기호 뒤의 숫자)
const priceRegex = /(?<=[$₩€¥£])\s*[\d,]+(?:\.\d+)?/g;
"$100, ₩10,000, €50.99".match(priceRegex);
// ["100", "10,000", "50.99"]
**기억하기 **: Named Group(
(?<name>...))은 캡처 결과를 이름으로 읽을 수 있어 가독성이 좋습니다. Lookbehind((?<=...))는 특정 패턴 뒤의 텍스트를 매칭합니다.u플래그는 유니코드 처리에,s플래그는 여러 줄 매칭에 필수입니다.
댓글 로딩 중...