JavaScript / substring()으로 문자열 추출하기와 slice 차이

String.substring(start, end)는 시작 인덱스부터 끝 인덱스 바로 앞까지의 새 문자열을 반환합니다. "123456789".substring(1, 4)의 결과는 "234"입니다. 끝 인덱스를 생략하면 끝까지 가져오며 원본 문자열은 바뀌지 않습니다.

시작은 포함하고 끝은 제외합니다

문자열 인덱스는 0부터 셉니다. substring(1, 4)는 인덱스 1·2·3을 가져오며 인덱스 4는 제외합니다. 두 인덱스가 같으면 빈 문자열이 나옵니다.

const text = '123456789';
console.log(text.substring(1, 4)); // '234'
console.log(text.substring(0, 4)); // '1234'
console.log(text.substring(2));    // '3456789'
console.log(text.substring(3, 3)); // ''
console.log(text);                 // 원본 그대로

두 번째 인수는 가져올 글자 수가 아니라 끝 위치입니다. 길이를 두 번째 인수로 쓰는 레거시 substr(start, length)와 구별하세요. 정확한 문법은 MDN의 String.substring() 문서에서 확인할 수 있습니다.

역순 인덱스와 음수 인덱스 처리

시작이 끝보다 크면 substring()은 두 인덱스를 바꿔서 처리합니다. 음수는 0으로 취급하고 문자열 길이를 넘는 값은 끝 위치로 맞춥니다. 따라서 음수로 문자열 끝에서 세고 싶다면 substring()이 아닌 slice()를 검토하세요.

console.log('abcdef'.substring(4, 1)); // 'bcd'
console.log('abcdef'.substring(-2, 2)); // 'ab'
console.log('abcdef'.substring(3, 99)); // 'def'
console.log('abcdef'.slice(4, 1));      // ''
console.log('abcdef'.slice(-2));        // 'ef'

인수 순서가 뒤집혀도 결과가 나오는 성질이 입력 오류를 가릴 수 있습니다. 사용자가 직접 고른 시작·끝 범위를 처리할 때는 의도적으로 순서를 교환할 것인지, 역순을 오류로 안내할 것인지 먼저 결정하세요.

전체 예제: 추출 범위를 확인하기

아래 코드를 UTF-8로 substring-example.html에 저장해 브라우저에서 엽니다. 문자열·시작·끝 위치를 입력하고 ‘추출’을 누릅니다. 끝을 비워 두면 끝까지 가져옵니다. 화면에는 추출 결과와 원본을 함께 보여 줍니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>substring 범위 실습</title>
</head>
<body>
<label for="source">원본 문자열</label>
<input id="source" type="text" value="123456789">
<label for="start">시작 인덱스</label>
<input id="start" type="number" step="1" value="1">
<label for="end">끝 인덱스(제외, 비우면 끝까지)</label>
<input id="end" type="number" step="1" value="4">
<button id="run" type="button">추출</button>
<button id="reset" type="button">처음으로</button>
<pre id="output" aria-live="polite"></pre>
<script>
const source = document.querySelector('#source');
const start = document.querySelector('#start');
const end = document.querySelector('#end');
const output = document.querySelector('#output');

function show() {
  const s = Number(start.value);
  const endText = end.value.trim();
  const e = endText === '' ? undefined : Number(endText);
  if (!Number.isInteger(s) || (e !== undefined && !Number.isInteger(e))) {
    output.textContent = '시작과 끝에 정수를 입력하세요. 끝은 비워 둘 수 있습니다.';
    return;
  }
  const result = e === undefined
    ? source.value.substring(s)
    : source.value.substring(s, e);
  output.textContent = `substring() 결과: ${JSON.stringify(result)}\n` +
    `원본: ${JSON.stringify(source.value)}`;
}

document.querySelector('#run').addEventListener('click', show);
document.querySelector('#reset').addEventListener('click', () => {
  source.value = '123456789';
  start.value = '1';
  end.value = '4';
  show();
});
show();
</script>
</body>
</html>

처음 입력 1과 4의 결과는 "234"입니다. 시작 4, 끝 1로 바꾸어도 인덱스가 교환되어 "234"가 나옵니다. 끝을 비우고 시작을 2로 지정하면 "3456789"입니다. ‘처음으로’를 누르면 입력과 결과가 복원됩니다. 원본은 어느 경우에도 "123456789"로 표시됩니다.

본문 일부를 미리 볼 때

문자열 일부를 보여 주는 UI에는 요소의 텍스트를 읽어 substring()을 적용할 수 있습니다. 이전 원고는 이 작업에 jQuery를 불러왔지만, 단일 요소라면 브라우저 기본 DOM API만으로 충분합니다. 문자열을 HTML로 삽입하지 않고 textContent에 대입하면 원본 텍스트가 태그로 실행되지 않습니다.

const paragraph = document.querySelector('#article-text');
const preview = document.querySelector('#preview');
if (paragraph && preview) {
  preview.textContent = paragraph.textContent.substring(0, 40);
}

여기서 40은 화면에 보이는 글자 수가 아니라 UTF-16 코드 단위의 위치입니다. 이모지나 결합 문자를 정확히 자르는 요구라면 코드 포인트나 문자소 단위의 처리를 따로 설계해야 합니다. 또한 문장 끝을 자연스럽게 표시해야 한다면 잘린 뒤의 단어·공백 처리와 생략 표시를 별도로 결정하세요.