JavaScript / 배열 indexOf()와 lastIndexOf()로 요소 위치 찾기

Array.indexOf()는 앞에서부터 일치하는 요소를 찾아 첫 인덱스를, Array.lastIndexOf()는 뒤에서부터 찾아 마지막 인덱스를 반환합니다. 인덱스는 0부터 시작하며, 찾지 못하면 둘 다 -1을 반환합니다. 검색 시작 인덱스를 지정하면 검사할 범위를 조절할 수 있습니다.

앞에서 찾기와 뒤에서 찾기

두 메서드의 기본 형식은 items.indexOf(value)와 items.lastIndexOf(value)입니다. 둘 다 원본 배열을 바꾸지 않습니다. 중복 값이 있을 때 어느 위치가 필요한지에 따라 선택하세요.

const items = ['a', 'b', 'a', 'c', 'a'];
console.log(items.indexOf('a'));     // 0
console.log(items.lastIndexOf('a')); // 4
console.log(items.indexOf('c'));     // 3
console.log(items.lastIndexOf('x')); // -1

첫 요소의 인덱스 0도 유효한 검색 결과입니다. 따라서 if (items.indexOf(value))처럼 참·거짓으로 검사하면 첫 요소를 찾았을 때 거짓으로 처리됩니다. 존재 여부는 !== -1로 검사하거나, 위치가 필요 없다면 includes()를 사용하세요. MDN의 Array.indexOf() 문서와 Array.lastIndexOf() 문서를 참고하세요.

fromIndex로 검색 범위 정하기

두 번째 인수 fromIndex는 검색을 시작할 인덱스입니다. indexOf()는 그 위치에서 오른쪽으로, lastIndexOf()는 그 위치에서 왼쪽으로 검사합니다. 값을 생략하면 각각 0과 마지막 인덱스부터 시작합니다. 같은 시작 인덱스를 주어도 진행 방향이 반대라는 점이 중요합니다.

const items = ['a', 'b', 'a', 'c', 'a'];
console.log(items.indexOf('a', 1));     // 2
console.log(items.lastIndexOf('a', 3)); // 2
console.log(items.indexOf('a', -2));    // 4
console.log(items.lastIndexOf('a', -2)); // 2

음수 fromIndex는 배열 끝에서 센 위치로 환산한 뒤, 각 메서드의 원래 방향으로 검색합니다. 위 배열 길이는 5이므로 -2는 인덱스 3을 뜻합니다. indexOf()는 3부터 오른쪽을 찾아 4를, lastIndexOf()는 3부터 왼쪽을 찾아 2를 반환합니다.

비교 규칙과 찾지 못하는 값

두 메서드는 타입을 자동으로 맞추지 않는 엄격한 일치 규칙으로 비교합니다. 문자열 "1"과 숫자 1은 서로 다른 값입니다. 별도로 만든 객체 두 개는 내용이 같아 보여도 같은 참조가 아니므로 일치하지 않습니다.

console.log(['1', 1].indexOf(1)); // 1
console.log([{ id: 1 }].indexOf({ id: 1 })); // -1
console.log([NaN].indexOf(NaN)); // -1
console.log([NaN].includes(NaN)); // true
console.log([NaN].findIndex(Number.isNaN)); // 0

NaN은 자기 자신과도 엄격하게 같지 않으므로 이 두 메서드로 찾을 수 없습니다. 존재 여부만 확인할 때는 includes(NaN), 실제 위치가 필요할 때는 findIndex(Number.isNaN)을 사용하세요. 비어 있는 배열 슬롯도 indexOf(undefined)로는 찾지 못하므로, 명시적으로 들어 있는 undefined와 구분해야 합니다.

전체 예제: 검색 방향과 시작점 바꾸기

아래 코드를 UTF-8로 array-index-search.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="value">찾을 문자열</label>
<input id="value" type="text" value="a">
<label><input id="use-start" type="checkbox"> 검색 시작 인덱스 사용</label>
<label for="start">시작 인덱스</label>
<input id="start" type="number" value="2" disabled>
<pre id="output" aria-live="polite"></pre>
<script>
const items = ['a', 'b', 'a', 'c', 'a'];
const value = document.querySelector('#value');
const useStart = document.querySelector('#use-start');
const start = document.querySelector('#start');
const output = document.querySelector('#output');

function show() {
  const term = value.value;
  const startIndex = Number(start.value);
  const first = useStart.checked
    ? items.indexOf(term, startIndex) : items.indexOf(term);
  const last = useStart.checked
    ? items.lastIndexOf(term, startIndex) : items.lastIndexOf(term);
  start.disabled = !useStart.checked;
  output.textContent = `배열: ${JSON.stringify(items)}\n` +
    `indexOf(${JSON.stringify(term)}): ${first}\n` +
    `lastIndexOf(${JSON.stringify(term)}): ${last}`;
}

value.addEventListener('input', show);
useStart.addEventListener('change', show);
start.addEventListener('input', show);
show();
</script>
</body>
</html>

기본값 a의 결과는 indexOf() = 0, lastIndexOf() = 4입니다. 시작 인덱스를 사용해 2를 입력하면 두 결과가 모두 2입니다. 3으로 바꾸면 앞 검색은 4, 뒤 검색은 2가 됩니다. 존재하지 않는 x를 입력하면 둘 다 -1입니다. 숫자형 값까지 시험하려면 입력값은 문자열이므로 배열과 입력의 자료형을 함께 바꿔야 합니다.