본문으로 건너뛰기
김신건의 로그

[JavaScript] String: 메서드, 정규식, 템플릿 리터럴

· 수정 · 📖 약 2분 · 691자/단어 #javascript #string #regex #fundamentals
JS String str pattern, JS String, String methods, template literal, 정규식, string pattern, JS 문자열 메서드, 문자열 패턴

정의

JavaScript 문자열은 불변 (immutable) UTF-16 시퀀스. 메서드는 항상 새 문자열을 반환하며, 원본을 수정하지 않는다. 문자열에서 원하는 위치/패턴을 찾거나, 교체하거나, 추출하는 작업이 프론트엔드와 백엔드 모두에서 빈번하다.

언제 쓰나

상황권장 도구
단순 포함 여부includes / startsWith / endsWith
위치 숫자 반환indexOf / lastIndexOf
복잡한 패턴 매칭정규식 (RegExp)
교체replace / replaceAll
분리split
추출slice / at

탐색 메서드 분류

flowchart TD
    A["문자열 탐색 목표"] --> B{"위치 숫자 필요?"}
    B -->|"Yes"| C["indexOf / lastIndexOf"]
    B -->|"No"| D{"불리언이면 충분?"}
    D -->|"Yes"| E["includes / startsWith / endsWith"]
    D -->|"No"| F{"복잡한 패턴?"}
    F -->|"단순"| G["includes + toLowerCase"]
    F -->|"복잡"| H["match / search / exec (RegExp)"]

기본 탐색 메서드

includes / startsWith / endsWith

ES2015+. 존재 여부만 알면 될 때 가장 의도가 명확하다.

const s = 'Hello, World!';

s.includes('World');          // true
s.includes('world');          // false (대소문자 구분)
s.startsWith('Hello');        // true
s.startsWith('World', 7);     // true (7번 인덱스부터 검사)
s.endsWith('!');              // true
s.endsWith('World', 12);      // true (처음 12자 안에서)

indexOf / lastIndexOf

위치(숫자)가 필요하거나, 같은 패턴이 여러 개 있을 때.

const s = 'abcabc';

s.indexOf('b');               // 1
s.lastIndexOf('b');           // 4
s.indexOf('b', 2);            // 4 (인덱스 2 이후 탐색)
s.indexOf('z');               // -1 (없으면 -1)

// 존재 여부 확인 (includes 가 더 가독성 높음)
s.indexOf('a') !== -1;        // true (구식)
s.includes('a');              // true (ES2015, 권장)

추출 메서드

const s = 'Hello, World!';

s.slice(7, 12);               // 'World'
s.slice(-6);                  // 'orld!'   (음수: 끝에서부터)
s.slice(-6, -1);              // 'orld'
s.substring(7, 12);           // 'World'   (음수 지원 안 함, 0으로 처리)
s.at(0);                      // 'H'
s.at(-1);                     // '!'       (ES2022, 음수 인덱스)

TIP

slice 는 음수 인덱스를 지원하고, substring 은 지원하지 않는다. 현대 코드에서는 sliceat 을 사용하자.

변환/조작 메서드

const s = '  Hello, World!  ';

s.trim();                     // 'Hello, World!'
s.trimStart();                // 'Hello, World!  '
s.trimEnd();                  // '  Hello, World!'
s.toLowerCase();              // '  hello, world!  '
s.toUpperCase();              // '  HELLO, WORLD!  '

'abc'.repeat(3);              // 'abcabcabc'
'5'.padStart(4, '0');         // '0005'
'5'.padEnd(4, '0');           // '5000'
'a,b,c'.split(',');           // ['a', 'b', 'c']
['a', 'b', 'c'].join('-');    // 'a-b-c'

replace / replaceAll

const s = 'Hello World World';

// 첫 번째만 교체
s.replace('World', 'JS');           // 'Hello JS World'

// 전체 교체
s.replaceAll('World', 'JS');        // 'Hello JS JS'

// 정규식 g 플래그로 전체 교체
s.replace(/World/g, 'JS');          // 'Hello JS JS'

// 교체 함수 (동적 치환)
'abc123'.replace(/\d+/, (match) => `[${match}]`);   // 'abc[123]'

// 캡처 그룹 참조 (역참조)
'2026-07-16'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1');  // '16/07/2026'

// 이름 캡처 그룹 참조
'2026-07-16'.replace(
    /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
    '$<d>/$<m>/$<y>'
);  // '16/07/2026'

IMPORTANT

replace 에 문자열 패턴을 넘기면 첫 번째 매칭만 교체한다. 전체 교체는 replaceAll 또는 정규식 /pattern/g.

정규식 (RegExp)

생성 방법

// 리터럴: 컴파일 타임, 패턴 고정
const re1 = /hello/i;

// 생성자: 런타임, 동적 패턴 구성 가능
const keyword = 'hello';
const re2 = new RegExp(keyword, 'i');

// 생성자에서 특수문자 이스케이프 주의
const dot = '.';
const escaped = new RegExp(dot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));

자주 쓰는 패턴

// 이메일 추출
const emailRe = /[\w.-]+@[\w.-]+\.\w{2,}/g;
'foo@bar.com, baz@qux.org'.match(emailRe);
// ['foo@bar.com', 'baz@qux.org']

// 숫자만 추출
'abc123def456'.match(/\d+/g);   // ['123', '456']

// 이름 캡처 그룹
const m = '2026-07-16'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m.groups;   // { year: '2026', month: '07', day: '16' }

// 선행 단언 (lookbehind)
'$100 €200'.match(/(?<=\$)\d+/g);   // ['100']

exec 반복 탐색

const re = /(\w+)=(\w+)/g;
const s = 'a=1 b=2 c=3';
let m;
while ((m = re.exec(s)) !== null) {
    console.log(`key=${m[1]}, val=${m[2]}`);
}
// key=a, val=1
// key=b, val=2
// key=c, val=3

matchAll (ES2020)

exec 반복 대신 더 깔끔한 방법.

const matches = [...'a=1 b=2'.matchAll(/(\w+)=(\w+)/g)];
matches.map(m => ({ key: m[1], val: m[2] }));
// [{ key: 'a', val: '1' }, { key: 'b', val: '2' }]

String 메서드 vs RegExp 메서드

메서드소속반환
str.match(re)String배열 또는 null
str.matchAll(re)StringRegExpStringIterator
str.search(re)String첫 매칭 인덱스 (없으면 -1)
str.replace(re, fn)String새 문자열
re.test(str)RegExpboolean
re.exec(str)RegExp배열 또는 null

템플릿 리터럴

기본 보간

const name = 'World';
const n = 42;

`Hello, ${name}!`;                        // 'Hello, World!'
`result: ${n * 2}`;                       // 'result: 84'
`3 > 2 is ${3 > 2}`;                     // '3 > 2 is true'

// 여러 줄 (개행 그대로)
const multi = `line 1
line 2`;

// 중첩 표현식
const items = [1, 2, 3];
`total: ${items.reduce((a, b) => a + b, 0)}`;   // 'total: 6'

태그드 템플릿 리터럴

함수가 템플릿을 직접 가공하는 고급 문법. styled-components, gql, sql 태그 등 라이브러리에서 활발히 사용한다.

function highlight(strings, ...values) {
    return strings.raw.reduce((result, str, i) => {
        const val = values[i - 1];
        return result + (val !== undefined ? `<em>${val}</em>` : '') + str;
    });
}

const name = 'Alice';
const age = 30;
highlight`이름: ${name}, 나이: ${age}`;
// '이름: <em>Alice</em>, 나이: <em>30</em>'

strings.raw 를 쓰면 이스케이프 시퀀스(\n, \t)를 처리하지 않은 원본 문자열을 받는다.

실전 예시

URL 슬러그 생성

function toSlug(text) {
    return text
        .toLowerCase()
        .trim()
        .replace(/[\s_]+/g, '-')
        .replace(/[^\w-]/g, '')
        .replace(/--+/g, '-');
}

toSlug('Hello  World!!');    // 'hello-world'
toSlug('Café au Lait');      // 'caf-au-lait'

민감 정보 마스킹

function maskEmail(email) {
    return email.replace(
        /^(.)(.*)(@.*)$/,
        (_, first, middle, domain) => first + '*'.repeat(middle.length) + domain
    );
}

maskEmail('alice@example.com');    // 'a****@example.com'

파일 확장자 추출

function getExt(filename) {
    return filename.match(/\.([^.]+)$/)?.[1] ?? '';
}

getExt('photo.jpg');       // 'jpg'
getExt('archive.tar.gz'); // 'gz'
getExt('noext');          // ''

한국어 포함 여부 확인

function hasKorean(text) {
    return /[\uAC00-\uD7A3\u1100-\u11FF\u3130-\u318F]/.test(text);
}

hasKorean('Hello 안녕');   // true
hasKorean('Hello World');  // false

함정

대소문자 구분

includes / indexOf 는 대소문자를 구분한다. 대소문자 무관 탐색은 toLowerCase() 후 비교하거나 /pattern/i 플래그.

const s = 'Hello World';

s.includes('hello');             // false
s.toLowerCase().includes('hello'); // true
/hello/i.test(s);                // true

replaceAll 에 정규식 쓸 때 g 플래그 필수

// ❌ g 플래그 없으면 TypeError
'aaa'.replaceAll(/a/, 'b');

// ✅ g 플래그 필수
'aaa'.replaceAll(/a/g, 'b');    // 'bbb'

WARNING

replaceAll 에 정규식을 넘길 때 g 플래그가 없으면 TypeError. 문자열 패턴이면 문제없다.

이모지/유니코드와 length

const s = '🎉';
s.length;              // 2 (UTF-16 surrogate pair)
[...s].length;         // 1 (코드 포인트 기준)

// 이모지 포함 문자열 반전
s.split('').reverse().join('');   // ❌ 깨짐
[...s].reverse().join('');        // ✅ 스프레드 활용

RegExp 인스턴스의 lastIndex 공유

const re = /\d+/g;

'abc123'.test(re);    // true  (lastIndex = 6)
'def456'.test(re);    // false (lastIndex 가 6 에서 계속 탐색, 즉시 실패)
re.lastIndex = 0;     // 수동 초기화 필요
'def456'.test(re);    // true

g 또는 y 플래그가 있는 정규식 인스턴스를 재사용하면 lastIndex 가 누적된다.

관련 위키

이 글의 용어 (5개)
[Javascript] 타입 변환 / 강제 변환javascript
정의 JavaScript 의 암묵적 형변환 (coercion). 연산자가 양쪽 피연산자의 타입을 자동으로 맞춘다. 명시적 변환과 구분. 3 가지 강제 변환 1. ToString …
[Javascript] Arrayjavascript
정의 JavaScript 의 는 순서 있는 객체. 정수 키 + . 풍부한 메서드 (map/filter/reduce/...) 가 함수형 스타일을 지원. 생성 핵심 메서드 (반복) …
[Javascript] Prototype Chainjavascript
정의 JavaScript 의 모든 객체는 (내부 슬롯) 을 가진다. 속성 lookup 시 자신에게 없으면 prototype chain 을 따라 올라가며 검색. 접근 함수의 pro…
[Javascript] Regular Expressionjavascript
정의 JavaScript 의 정규식. 객체 또는 리터럴 로 표현. ECMAScript 정규식 문법 (Perl 의 부분집합). 생성 리터럴이 가독성 좋음. 동적 패턴은 . flag…
[Javascript] Stringjavascript
정의 JavaScript 의 은 UTF-16 코드 유닛의 불변 시퀀스. 작은따옴표, 큰따옴표, 백틱 (template literal) 으로 작성. 사용 상황 | 상황 | 권장 패…

이 개념을 다룬 위키 페이지 (1)

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기