junyeokk
Blog
JavaScript·2026. 01. 25

JavaScript 반복문 비교

개요

JavaScript에는 다양한 반복문이 있다. for, for...in, for...of, forEach, while 등 상황에 따라 적합한 반복문이 다르다. 이 문서에서는 각 반복문의 특징과 차이점을 살펴보고, 언제 어떤 반복문을 써야 하는지 정리한다. 특히 정규식 매칭에서 while + exec 대신 for...of + matchAll을 사용하는 이유도 다룬다.


반복문 종류 한눈에 보기

문법용도특징
for인덱스 기반 순회가장 유연하다. break/continue 가능
for...in객체 키 순회프로토타입 체인까지 순회한다
for...ofiterable 값 순회ES6+. 가장 깔끔하다
forEach배열 메서드break 불가. 콜백 스타일
while조건 기반무한 루프 가능

각 반복문이 어떤 상황에서 빛을 발하는지 살펴보자.


for: 전통적인 인덱스 기반 순회

for는 가장 오래된 반복문이다. 인덱스가 필요하거나 복잡한 조건을 다뤄야 할 때 사용한다.

javascript
const arr = ['a', 'b', 'c'];

for (let i = 0; i < arr.length; i++) {
  console.log(i, arr[i]);
}
// 0 'a'
// 1 'b'
// 2 'c'

이 코드는 배열의 인덱스와 값을 함께 출력한다. i를 직접 조작할 수 있어서 역순 순회나 2칸씩 건너뛰기 같은 복잡한 패턴도 구현할 수 있다. 다만 단순히 값만 순회한다면 for...of가 더 깔끔하다.


for...in: 객체 키 순회

for...in은 객체의 열거 가능한 속성(키)을 순회한다.

javascript
const obj = { a: 1, b: 2, c: 3 };

for (const key in obj) {
  console.log(key, obj[key]);
}
// 'a' 1
// 'b' 2
// 'c' 3

위 코드는 객체의 각 키와 값을 출력한다. 그런데 배열에 for...in을 사용하면 어떻게 될까?

javascript
const arr = ['x', 'y', 'z'];

for (const index in arr) {
  console.log(typeof index, index);
}
// 'string' '0'
// 'string' '1'
// 'string' '2'

인덱스가 숫자가 아니라 문자열로 나온다. 배열도 객체이기 때문에 for...in은 배열의 인덱스를 "키"로 취급한다.

더 큰 문제는 프로토타입 체인에 있는 속성까지 순회한다는 점이다:

javascript
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};

const arr = [3, 5, 7];
arr.foo = 'hello';

for (const i in arr) {
  console.log(i);
}
// '0', '1', '2', 'foo', 'arrCustom', 'objCustom'

for (const i of arr) {
  console.log(i);
}
// 3, 5, 7

for...in은 배열 요소뿐 아니라 직접 추가한 foo, 프로토타입의 arrCustom, objCustom까지 전부 순회한다. for...of는 배열 값만 깔끔하게 순회한다. 배열에는 for...in을 사용하지 않는 게 좋다.


for...of: iterable 값 순회

for...of는 ES6에서 도입됐다. iterable 객체의 값을 순회한다. 배열, 문자열, Map, Set 등 Symbol.iterator를 구현한 모든 객체에서 사용할 수 있다.

javascript
const arr = ['a', 'b', 'c'];

for (const value of arr) {
  console.log(value);
}
// 'a'
// 'b'
// 'c'

값만 필요할 때 가장 깔끔한 문법이다. 인덱스도 함께 필요하다면 entries() 메서드를 사용한다:

javascript
const arr = ['a', 'b', 'c'];

for (const [index, value] of arr.entries()) {
  console.log(index, value);
}
// 0 'a'
// 1 'b'
// 2 'c'

entries()[인덱스, 값] 형태의 iterator를 반환한다. 구조 분해 할당과 함께 사용하면 인덱스와 값을 동시에 얻을 수 있다. for...in과 달리 프로토타입 체인 문제도 없어서, 현대 JavaScript에서 배열 순회의 기본 선택이다.


forEach: 배열 전용 메서드

forEach는 배열의 메서드다. 콜백 함수를 받아서 각 요소에 대해 실행한다.

javascript
const arr = ['a', 'b', 'c'];

arr.forEach((value, index) => {
  console.log(index, value);
});
// 0 'a'
// 1 'b'
// 2 'c'

for...of와 비슷해 보이지만 중요한 차이가 있다. forEachbreak나 continue를 사용할 수 없다. 콜백 함수 내에서 return을 해도 루프 전체가 아니라 해당 콜백만 종료된다.

javascript
const arr = [1, 2, 3, 4, 5];

// 이렇게 하면 break처럼 동작하지 않는다
arr.forEach(value => {
  if (value === 3) return; // continue처럼 동작
  console.log(value);
});
// 1
// 2
// 4
// 5

중간에 루프를 멈춰야 한다면 for...of나 일반 for를 사용해야 한다. forEach는 모든 요소를 빠짐없이 처리할 때, 혹은 map, filter 같은 함수형 스타일과 일관성을 유지하고 싶을 때 적합하다.

또 하나 주의할 점이 있다. forEach는 async 콜백을 기다리지 않는다:

javascript
const urls = ['url1', 'url2', 'url3'];

// 잘못된 코드
urls.forEach(async (url) => {
  const data = await fetch(url);
  console.log(data);
});
console.log('완료'); // fetch가 끝나기 전에 출력됨

// 올바른 코드
for (const url of urls) {
  const data = await fetch(url);
  console.log(data);
}
console.log('완료'); // 모든 fetch 후 출력됨

forEach는 콜백이 Promise를 반환해도 그걸 await하지 않고 다음 반복으로 넘어간다. 비동기 작업을 순차적으로 처리해야 한다면 for...of를 사용해야 한다.


정규식 매칭: exec vs matchAll

정규식으로 문자열에서 여러 매칭을 찾을 때 전통적으로 while + exec 조합을 사용했다. 하지만 ES2020에서 도입된 matchAll을 사용하면 더 깔끔하게 작성할 수 있다.

while + exec: 전통적인 방식

javascript
const regex = /\d+/g;
const str = 'a1b22c333';
let match;

while ((match = regex.exec(str)) !== null) {
  console.log(match[0]);
}
// '1'
// '22'
// '333'

이 코드에는 몇 가지 문제가 있다:

  1. 상태 의존: regex.exec()는 정규식 객체의 lastIndex를 변경한다. 같은 정규식을 다른 곳에서 재사용하면 예상치 못한 결과가 나올 수 있다.
  2. 복잡한 조건: while 조건에 할당문이 들어가서 읽기 어렵다.
  3. 변수 선언 필요: let match를 미리 선언해야 한다.

for...of + matchAll: 모던한 방식

javascript
const regex = /\d+/g;
const str = 'a1b22c333';

for (const match of str.matchAll(regex)) {
  console.log(match[0]);
}
// '1'
// '22'
// '333'

matchAll은 iterator를 반환하므로 for...of로 순회할 수 있다. 코드가 훨씬 깔끔하고, 정규식 객체의 상태를 변경하지 않아서 안전하다.

실제 리팩토링 예시

코드블록 추출 로직을 리팩토링한 사례를 보자:

typescript
// before: while + exec
const codeBlockRegex = /

리팩토링 후 코드가 더 선언적이고 읽기 쉬워졌다. let match 선언도 필요 없고, while 조건의 복잡한 할당문도 사라졌다.


언제 뭘 쓸까: 상황별 추천

상황추천 반복문
배열 값만 순회for...of
배열 값 + 인덱스 필요for...of + entries() 또는 forEach
객체 키-값 순회for...of + Object.entries()
정규식 다중 매칭for...of + matchAll
중간에 break 필요for, for...of, while
복잡한 인덱스 조작for
모든 요소 처리 (함수형)forEach

정리

  • for...of가 현대 JavaScript에서 가장 범용적인 선택이다
  • for...in은 객체 키 순회에만 사용한다. 배열에는 쓰지 않는다
  • forEach는 break가 필요 없을 때 사용한다
  • 정규식 매칭은 matchAll + for...of 조합이 가장 깔끔하다
  • 복잡한 순회 조건이 필요하면 전통적인 for를 사용한다