Array.prototype.includes vs indexOf 성능 비교

배경

레거시 코드를 리팩토링하면서 배열 포함 여부 체크 로직을 ES6 문법으로 바꾸는 작업을 진행했다. 기존에는 indexOf() !== -1 패턴을 사용했는데, includes()가 가독성 면에서 훨씬 나아 보였다.

// 기존 코드
if (allowedRoles.indexOf(userRole) !== -1) {
  // ...
}

// 변경 후
if (allowedRoles.includes(userRole)) {
  // ...
}

하지만 이 메서드가 비교적 최근에 추가된 것이라 성능이 걱정됐다. 특히 우리 서비스는 권한 체크를 매 API 요청마다 수행하기 때문에 작은 차이도 누적되면 부담이 될 수 있다.

벤치마크 결과

Node 8.5 환경에서 10만 번씩 반복 테스트를 진행했다.

const arr = ['admin', 'user', 'guest', 'moderator', 'editor'];
const target = 'moderator';

// indexOf: 평균 2.3ms
console.time('indexOf');
for (let i = 0; i < 100000; i++) {
  arr.indexOf(target) !== -1;
}
console.timeEnd('indexOf');

// includes: 평균 2.4ms
console.time('includes');
for (let i = 0; i < 100000; i++) {
  arr.includes(target);
}
console.timeEnd('includes');

결과는 거의 동일했다. 배열 크기를 100개, 1000개로 늘려봐도 유의미한 차이는 없었다.

결론

성능 차이가 미미하므로 가독성을 우선해 includes()를 사용하기로 결정했다. 다만 IE 지원이 필요한 프로젝트는 Babel polyfill 설정을 확인해야 한다. 우리는 이미 babel-preset-env를 사용 중이라 문제없었다.

참고로 includes()는 NaN 비교도 제대로 처리한다는 장점이 있다. indexOf()는 NaN을 찾지 못하지만, includes()는 찾아낸다.