본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev17

숫자 배열을 sort()로 정렬했더니 100000이 21보다 앞에 온 이유

  • #Common Pitfall
  • #Engineering Note
  • #JavaScript

문제 발생

조회수 순으로 정렬했는데 순서가 엉망이었습니다.

const views = [1, 30, 4, 21, 100000];
views.sort();
console.log(views); // [1, 100000, 21, 30, 4]

한 자리 수만 있는 테스트 데이터에서는 정상으로 보였습니다.

원인 분석

기본 비교자는 문자열 비교입니다. MDN이 그대로 적습니다 — 기본 정렬 순서는 오름차순이며, 요소를 문자열로 변환한 다음 그 UTF-16 코드 단위 값의 순서를 비교하는 방식입니다. 문서의 예제 결과가 위와 정확히 같습니다.

문자열로 보면 "100000""21"보다 앞섭니다. 첫 글자 12를 비교하는 순간 결정되기 때문입니다. 테스트 데이터가 한 자리 수뿐이었다면 문자열 순서와 숫자 순서가 우연히 일치해 문제가 드러나지 않습니다.

함께 알아둘 규칙이 셋 더 있습니다.

  • undefined는 항상 끝으로 갑니다. 비교 함수와 무관합니다.
  • 정렬은 안정적입니다. ES2019부터 명세가 안정 정렬을 요구하므로, 같은 값끼리는 원래 순서가 유지됩니다.
  • sort()는 제자리에서 정렬하고 같은 배열의 참조를 반환합니다. 원본이 바뀝니다.

해결 방안

  1. 숫자에는 비교 함수를 줍니다.
views.sort((a, b) => a - b);          // 오름차순
views.sort((a, b) => b - a);          // 내림차순
  1. 원본을 지켜야 하면 toSorted()를 씁니다. React state처럼 불변으로 다뤄야 하는 값에서는 이게 기본입니다.
const sorted = views.toSorted((a, b) => a - b);
  1. 문자열 정렬에는 localeCompare를 씁니다. 기본 비교는 코드 단위 순서라 한글·악센트 문자에서 사람이 기대하는 순서와 다릅니다.
names.sort((a, b) => a.localeCompare(b, "ko"));

목록이 크면 Intl.Collator를 만들어 compare를 재사용하는 편이 빠릅니다.

  1. 여러 기준은 앞 기준의 결과가 0일 때만 다음으로 넘깁니다.
posts.sort((a, b) => b.likes - a.likes || a.title.localeCompare(b.title, "ko"));
  1. 비교 함수가 일관적인지 확인합니다. 같은 두 값에 대해 항상 같은 부호를 돌려줘야 합니다 — 안에서 현재 시각이나 난수를 읽으면 결과가 실행마다 달라집니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.