Array.prototype.includes와 indexOf 성능 비교
문제 상황
레거시 코드베이스에서 배열 포함 여부를 체크하는 로직이 indexOf() !== -1 패턴으로 가득했다. ES2016 스펙이 안정화되면서 includes()를 도입할 수 있게 되어 리팩토링을 진행했다.
// 기존 코드
if (allowedRoles.indexOf(userRole) !== -1) {
// ...
}
// 변경 후
if (allowedRoles.includes(userRole)) {
// ...
}
성능 측정
1만 개 요소 배열에서 1만 번 탐색하는 간단한 벤치마크를 돌렸다.
const arr = Array.from({ length: 10000 }, (_, i) => i);
const target = 9999;
// indexOf 방식
console.time('indexOf');
for (let i = 0; i < 10000; i++) {
arr.indexOf(target) !== -1;
}
console.timeEnd('indexOf'); // ~45ms
// includes 방식
console.time('includes');
for (let i = 0; i < 10000; i++) {
arr.includes(target);
}
console.timeEnd('includes'); // ~47ms
결과는 거의 동일했다. V8 엔진이 내부적으로 같은 알고리즘을 사용하는 것으로 보인다.
includes의 장점
성능보다 중요한 건 NaN 처리였다.
[1, 2, NaN].indexOf(NaN); // -1
[1, 2, NaN].includes(NaN); // true
indexOf는 === 비교를 사용해서 NaN을 찾지 못한다. includes는 SameValueZero 알고리즘을 사용해 제대로 처리한다.
결론
코드 가독성과 정확성 측면에서 includes를 사용하는 게 맞다고 판단했다. Babel 트랜스파일 설정도 이미 되어있어서 IE 지원도 문제없었다. 점진적으로 기존 indexOf 패턴을 교체하기로 했다.