JavaScript 개발을 하다 보면 한 배열에서 다른 배열에 포함된 요소들을 제거해야 하는 경우가 자주 발생합니다. 이번 글에서는 두 개의 배열을 인자로 받아, 첫 번째 배열에는 존재하지만 두 번째 배열에는 없는 요소들만 담은 새로운 배열을 반환하는 함수를 작성해 보겠습니다.
접근 방식
이 문제는 Array.prototype.filter() 메서드를 활용하면 간단하게 해결할 수 있습니다. filter()는 콜백 함수가 true를 반환하는 요소들만 모아 새로운 배열을 만들어 주기 때문입니다.
여기에 각 요소가 두 번째 배열에 존재하는지 여부를 판별하기 위해 Array.prototype.includes() 메서드를 함께 사용합니다. includes()는 해당 요소가 배열에 있으면 true, 없으면 false를 반환하므로, 이 값을 뒤집어(!) 원하지 않는 요소를 걸러낼 수 있습니다.
예제 코드
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [1, 3, 5];
const filterUnwanted = (arr1 = [], arr2 = []) => {
const filtered = arr1.filter(el => !arr2.includes(el));
return filtered;
};
console.log(filterUnwanted(arr1, arr2));실행 결과
콘솔에 출력되는 결과는 다음과 같습니다.
[2, 4]
동작 원리 살펴보기
위 코드가 실행되는 과정을 단계별로 정리하면 다음과 같습니다.
1. filter()가 arr1의 각 요소를 순회하며 콜백 함수를 호출합니다.
2. 콜백 함수 내부에서 arr2.includes(el)로 현재 요소가 두 번째 배열에 있는지 확인합니다.
3. 두 번째 배열에 없는 요소(includes()가 false를 반환하는 경우)만 최종 배열에 포함됩니다.
4. 그 결과 arr2에 포함된 1, 3, 5는 제거되고 [2, 4]만 남게 됩니다.
indexOf()를 사용한 대안
includes() 대신 indexOf()를 사용할 수도 있습니다. indexOf()는 요소가 존재하면 해당 인덱스를, 존재하지 않으면 -1을 반환하므로 아래와 같이 작성할 수 있습니다.
const filterUnwanted = (arr1 = [], arr2 = []) => {
return arr1.filter(el => arr2.indexOf(el) === -1);
};두 방법 모두 동일한 결과를 반환하지만, includes()가 의도를 더 명확하게 표현하므로 가독성 측면에서 권장됩니다. 참고로 includes()는 ES2016(ES7)부터 지원되므로, 구형 브라우저 환경을 지원해야 한다면 indexOf() 방식을 사용하는 것이 안전합니다.
마무리
이처럼 filter()와 includes()(또는 indexOf())를 조합하면 별도의 반복문 없이 선언적으로 깔끔하게 배열 차집합을 구현할 수 있습니다. 실무에서는 배열 크기가 매우 클 경우 Set 객체를 활용해 조회 성능을 O(1)로 개선하는 방법도 고려해 보시기 바랍니다.