JavaScript / substr() 사용법과 slice·substring 대체 방법

String.substr(start, length)는 시작 위치부터 지정한 길이만큼 문자열을 가져옵니다. 예를 들어 "123456789".substr(1, 5)는 "23456"입니다. 다만 이 메서드는 레거시 기능이므로 새 코드에는 표준적인 slice()나 substring()을 선택하는 편이 좋습니다.

두 번째 인수는 끝 위치가 아닌 길이입니다

문자열 인덱스는 0부터 시작합니다. start가 1이면 두 번째 위치에서 시작하고 length가 5이면 다섯 코드 단위를 가져옵니다. 길이를 생략하면 그 위치부터 끝까지 반환합니다. 원본 문자열은 변하지 않습니다.

const text = '123456789';
console.log(text.substr(1, 5)); // '23456'
console.log(text.substr(0, 5)); // '12345'
console.log(text.substr(3));    // '456789'
console.log(text);              // '123456789'

예전 원고의 코드에는 substr()를 호출하면서 출력 라벨을 substring()으로 쓴 부분이 있었습니다. 두 메서드는 두 번째 인수의 의미가 달라 같은 이름으로 표시하면 결과를 오해하기 쉽습니다. MDN의 String.substr() 문서에서 현재 상태와 인수 규칙을 확인할 수 있습니다.

음수 시작 위치와 경계값

시작 위치가 음수이면 문자열 끝에서 거꾸로 셉니다. -2는 끝에서 두 번째 코드 단위입니다. 길이가 0이나 음수이면 빈 문자열이고, 시작 위치가 문자열 길이 이상이어도 빈 문자열입니다. 시작 위치가 지나치게 작은 음수이면 맨 앞으로 맞춰집니다.

console.log('123456789'.substr(-2, 2)); // '89'
console.log('123456789'.substr(2, 0));  // ''
console.log('123456789'.substr(2, -1)); // ''
console.log('123456789'.substr(99, 2)); // ''

substr()의 인덱스와 길이는 자바스크립트의 UTF-16 코드 단위 기준입니다. 이모지나 일부 결합 문자를 사람이 보는 한 글자로 정확히 자르는 데 적합하지 않습니다. 글자 단위 처리가 목적이라면 코드 포인트나 문자소 단위를 별도로 선택해야 합니다.

전체 예제: 기존 코드와 대체 코드를 비교하기

아래 코드를 UTF-8로 substr-example.html에 저장하고 브라우저에서 엽니다. 문자열·시작 위치·길이를 입력한 뒤 ‘비교’를 누르세요. 시작과 길이가 모두 0 이상일 때에만 단순한 slice(start, start + length) 대체 결과를 함께 보여 줍니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>substr와 slice 비교</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="length">가져올 길이(비우면 끝까지)</label>
<input id="length" type="number" step="1" value="5">
<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 length = document.querySelector('#length');
const output = document.querySelector('#output');

function show() {
  const s = Number(start.value);
  const lengthText = length.value.trim();
  const count = lengthText === '' ? undefined : Number(lengthText);
  if (!Number.isInteger(s) || (count !== undefined && !Number.isInteger(count))) {
    output.textContent = '시작 위치와 길이에 정수를 입력하세요. 길이는 비워 둘 수 있습니다.';
    return;
  }
  const oldResult = count === undefined
    ? source.value.substr(s)
    : source.value.substr(s, count);
  let alternative = '음수 인수는 적용 범위를 검토한 뒤 변환하세요.';
  if (s >= 0 && (count === undefined || count >= 0)) {
    const newResult = count === undefined
      ? source.value.slice(s)
      : source.value.slice(s, s + count);
    alternative = `slice() 결과: ${JSON.stringify(newResult)}`;
  }
  output.textContent = `substr() 결과: ${JSON.stringify(oldResult)}\n${alternative}`;
}

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

기본 입력에서는 두 방법 모두 "23456"을 출력합니다. 길이를 비우면 끝까지 가져옵니다. 시작을 -2로 바꾸면 substr()의 결과는 "89"이지만 대체 코드는 자동으로 제안하지 않습니다. ‘처음으로’를 누르면 기본 입력과 결과로 복원됩니다. 이 비교는 어떤 조건에서 두 식이 같아지는지를 확인하기 위한 것입니다.

새 코드로 옮길 때의 선택 기준

시작과 길이가 항상 0 이상인 코드라면 text.substr(start, length)를 text.slice(start, start + length)로 바꾸고 같은 입력으로 결과를 비교할 수 있습니다. 음수 시작 위치나 길이가 들어올 수 있다면 그 식으로 일괄 치환하면 안 됩니다. 예를 들어 음수 길이는 substr()에서 빈 문자열이지만 slice()의 끝 위치 계산은 다른 결과를 낼 수 있습니다.

const text = '01234';
console.log(text.substr(1, -2));      // ''
console.log(text.slice(1, 1 + -2));   // '123'
console.log(text.substring(1, 1 + -2)); // '0'

기존 동작을 보존해야 한다면 실제 입력 범위와 음수 처리 규칙을 먼저 정리하고 테스트한 뒤 바꾸세요. 새 코드에서는 끝 인덱스로 범위를 표현하고 싶으면 slice(start, end), 음수 인덱스를 쓰지 않고 양쪽 인덱스를 다루려면 substring(start, end)를 검토하세요. 두 메서드 역시 음수와 역순 인수 처리가 서로 다릅니다. MDN의 substring() 비교 설명을 참고할 수 있습니다.