Medium - Should You Stop Using .forEach() in Your JavaScript Code?
in Trend
Trend 파악을 위한 Medium 기고문 포스팅 - 자바스크립트 코드에서 forEach()를 그만써야 할까요?
Image source: Author
Introduction
PHP로 프로그래밍을 시작한 저에게 .forEach() 메소드는 그냥 단순히 배열을 순회하는 것으로 이해했습니다. 저 스스로 forEach는 표준 for
구문의 구현과 완전히 똑같다고 생각했죠. 그러나 제가 자바스크립트로 코드를 짜게 되면서 그 둘 사이에 차이점이 있다는 것을 알게 되었습니다. 이 기사에서는 for
루프와 forEach()
메소드의 차이점 및 각각 어떤 장단점이 있는지 알려드리겠습니다.
그리고 제목을 너무 그대로 받아들이지 마세요. 이 기사의 목표는 독자들에게 forEach를 쓰면 병목현상이 생길 수도 있기 때문에 언제 쓸 수 있고 언제 쓰지 말아햐 하는지 알려주고자 하는 것입니다. 그럼 시작하겠습니다.
How forEach works
forEach
메소드는 여러분의 입력값을 콜백 함수로 받아서 각 배열의 원소를 순회할 때마다 이 콜백함수를 실행합니다. 주목해야 할 점은 추가적으로 전달할 수 있는 매개변수가 있다는 것입니다. 옵셔널 매개변수는 현재 값의 인덱스 입니다. forEach
함수는 콜백 내부에서 this를 정의하는 매개변수도 제공합니다.
위의 결과는 다음과 같을 겁니다.
corgis - 0
are - 1
cool - 2
Short-Circuiting
short-circuiting 은 우리가 루프를 중지하거나 건너뛰는 것을 말합니다. 우리가 forEach()
를 쓰면 이런 short-circuiting이 불가능합니다. 루프의 모든 경우에 대해서 선형적인 실행시간으로 수행될 것입니다.
왜 이것을 신경써야할까요? 만약 우리가 정렬되지 않은 100만개의 원소를 가진 배열이 있고 특정 원소를 찾는다고 가정해 봅시다. 만약 첫 순회에서 원하는 원소를 찾이면 운이 좋은 것이죠. 그래서 우리가 원하는 값을 찾았으니까 바로 return을 하고 싶을 겁니다. 그러나 forEach()
가 구현된 방식에서는 항상 나머지 배열 원소까지 모두 순회하게 됩니다. 만약 이런 문제를 해결할 때는 .findIndex()
메소드를 사용하는 것이 좋습니다.
Performance
forEach()
메소드에서는 매 순회마다 콜백함수를 호출하므로 for
루프에 비해서 추가적인 scope 오버헤드가 발생하고 속도도 느려지게 됩니다. for
루프는 초기화 구문이 있고 매 순회마다 체크하는 조건 구문이 있으며 그 다음에 바디 부분이 실행되고 루프의 카운트가 올라갑니다. forEach
구문과 비교하면 우리는 매번 순회할 때마다 추가적인 함수를 호출하지 않으니 훨씬 속도가 빠릅니다.
성능을 측정하기 위해서 배열을 초기화한 뒤에 시간을 재는 스크립트를 만들었습니다. 각 루프들은 단순히 O(1)의 복잡도를 가지고 있습니다.
Readability
소프트웨어 개발에 있어서 유지보수가 용이하고 가독성이 좋은 코드를 짜는게 무엇보다 중요합니다. 제 생각에는 여러분이 가독성 때문에 계속해서 forEach()
를 쓰시는 것 같습니다. 자바스크립트에서는 배열에다 메소드 체이닝을 하는게 대부분이고 그럴 때는 for
루프를 쓰는 것보다 forEach()
루프를 쓰는 것이 훨씬 낫죠.
그리고 위와 같이 엄청나게 큰 입력이 아니라면 사실 forEach()
와 for
루프의 속도는 비슷합니다. 몇 밀리세컨드를 위해서 가독성을 포기할순 없겠죠?
Conclusion
ECMAScript의 버전이 올라오면서 다양한 메소드들이 나오고 있습니다. 저는 적절한 도구를 사용하는 것이 중요하다고 생각합니다. 만약 엄청나게 큰 배열을 다뤄야 한다면 네이티브 for
루프를 사용하는 것이 좋겠죠. 그렇지 않다면 좀 성능이 느려도 .forEach()
를 쓰거나 다른 ES배열 메소드를 사용해도 전혀 해가 될 게 없습니다. 가독성과 성능 중 어떤 것이 우선되어야 하는 상황인지 잘 고려해서 쓰시면 될 것 같습니다.
Summary
- for 루프가 .forEach()를 비롯한 각종 배열 메소드 보다 빠름
- 그렇지만 메소드 체이닝이나 가독성을 고려하면 고차원 iterator를 쓰는게 좋음
- 아주 큰 배열이 아니면 고차원 배열 순회 메소드를 쓰는 것이 좋을 것