JavaScript / slice()로 배열 일부 복사하기와 음수 인덱스

Array.slice(start, end)는 배열의 start부터 end 바로 앞까지를 새 배열로 복사합니다. 원본 배열은 변경하지 않습니다. 끝 인덱스를 생략하면 마지막 요소까지 선택하며, 음수 인덱스는 배열 끝에서 거꾸로 셉니다.

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

배열 인덱스는 0부터 시작합니다. 따라서 slice(1, 4)는 두 번째, 세 번째, 네 번째 요소인 인덱스 1·2·3을 가져옵니다. end 위치의 요소는 포함하지 않습니다.

const items = ['One', 'Two', 'Three', 'Four', 'Five', 'Six'];
const selected = items.slice(1, 4);

console.log(selected); // ['Two', 'Three', 'Four']
console.log(items);    // 원본은 여전히 여섯 요소
console.log(items.slice(3, 3)); // []

정규화한 끝 위치가 시작 위치와 같거나 앞이면 빈 배열을 반환합니다. 시작 위치가 배열 길이 이상이어도 빈 배열입니다. 반환값을 사용하지 않으면 원본이나 화면에는 아무 변화가 없습니다. MDN의 Array.slice() 문서에서 인덱스 경계 규칙을 확인할 수 있습니다.

끝 생략과 음수 인덱스

end를 생략하면 배열 끝까지 복사합니다. -1은 마지막 요소의 위치, -2는 끝에서 두 번째 위치를 뜻합니다. 음수 끝 위치도 제외되므로 slice(-4, -1)에는 마지막 요소가 들어가지 않습니다.

console.log(items.slice(2));      // ['Three', 'Four', 'Five', 'Six']
console.log(items.slice(-2));     // ['Five', 'Six']
console.log(items.slice(-4, -1)); // ['Three', 'Four', 'Five']
console.log(items.slice());       // 전체를 새 배열로 복사

인덱스가 범위를 벗어나면 가능한 배열 경계로 처리됩니다. 예를 들어 slice(-100, 100)은 이 배열 전체를 돌려줍니다. 첫 인수에 undefined를 주거나 인수를 모두 생략해도 시작은 0입니다.

전체 예제: 선택 범위를 바꾸어 보기

아래 코드를 UTF-8로 array-slice-example.html에 저장하고 브라우저에서 엽니다. 시작과 끝에 정수를 입력해 ‘선택’을 누르면 선택 결과와 원본이 함께 표시됩니다. 끝 입력을 비우면 끝까지 복사합니다.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>배열 일부 선택하기</title>
</head>
<body>
<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="select" type="button">선택</button>
<button id="reset" type="button">처음으로</button>
<pre id="output" aria-live="polite"></pre>
<script>
const items = ['One', 'Two', 'Three', 'Four', 'Five', 'Six'];
const startInput = document.querySelector('#start');
const endInput = document.querySelector('#end');
const output = document.querySelector('#output');

function show() {
  const start = Number(startInput.value);
  const endText = endInput.value.trim();
  const end = endText === '' ? undefined : Number(endText);
  if (!Number.isInteger(start) || (end !== undefined && !Number.isInteger(end))) {
    output.textContent = '시작과 끝에 정수를 입력하세요. 끝은 비워 둘 수 있습니다.';
    return;
  }
  const selected = items.slice(start, end);
  output.textContent = `원본: ${JSON.stringify(items)}\n` +
    `선택: ${JSON.stringify(selected)}\n` +
    `원본 길이: ${items.length}`;
}

document.querySelector('#select').addEventListener('click', show);
document.querySelector('#reset').addEventListener('click', () => {
  startInput.value = '1';
  endInput.value = '4';
  show();
});
show();
</script>
</body>
</html>

처음 값 1과 4에서는 ["Two","Three","Four"]가 나옵니다. 끝을 비우고 시작을 2로 바꾸면 ["Three","Four","Five","Six"], 시작을 -2로 바꾸면 ["Five","Six"]가 나옵니다. ‘처음으로’를 누르면 입력과 결과가 복원됩니다. 원본 길이 6이 계속 표시되어 slice()가 원본을 자르지 않았다는 점도 확인할 수 있습니다.

새 배열이지만 내부 객체까지 복제하지는 않습니다

slice()는 얕은 복사입니다. 숫자·문자열 같은 값은 새 배열에 복사되지만 객체 요소는 같은 객체를 가리킵니다. 그래서 새 배열에서 객체의 속성을 바꾸면 원본 배열이 담은 객체에서도 변경이 보입니다. 반면 새 배열의 요소 자체를 다른 객체로 대입하면 원본 배열의 해당 칸은 바뀌지 않습니다.

const people = [{ name: '민지' }];
const copy = people.slice();
copy[0].name = '유나';
console.log(people[0].name); // '유나'

copy[0] = { name: '하나' };
console.log(people[0].name); // 여전히 '유나'

원본 배열의 항목을 실제로 제거하거나 삽입하려면 splice()를 사용합니다. 원본을 보존하면서 일부를 읽어야 한다면 slice()가 맞습니다. 복사본의 중첩 객체를 독립적으로 바꿔야 한다면 필요한 깊이까지 별도로 복제해야 합니다.