반복문
개요
반복문이란?
반복문(Loop)은 프로그래밍에서 특정 코드 블록을 조건이 만족할 때까지 반복 실행하는 제어 구조입니다. JavaScript에서는 다양한 반복문 구문을 제공하여 배열, 객체, 이터러블(iterable) 등의 데이터를 효율적으로 처리할 수 있도록 지원합니다.
JavaScript에서의 역할
JavaScript의 반복문은 다음과 같은 상황에서 핵심적인 역할을 합니다:
- 배열/객체의 요소 순회
- 비동기 작업의 반복 처리 (타이머, 네트워크 요청 등)
- 알고리즘 구현 (탐색, 정렬 등)
- 사용자 입력 검증 등 반복적인 작업 자동화
반복문의 종류
for 반복문
가장 일반적인 반복문 구조로, 초기화 - 조건 검사 - 증감의 3단계를 포함합니다.
문법
for (초기화; 조건; 증감) {
// 반복 실행할 코드
}
예시
for (let i = 0; i < 5; i++) {
console.log(`반복 횟수: ${i}`);
}
주의사항
var 대신 let 사용 시 블록 스코프로 안전하게 변수 관리 가능
- 무한 루프 방지를 위해 증감식 누락 주의
while 반복문
조건이 참일 동안 코드 블록을 반복 실행합니다.
문법
while (조건) {
// 반복 실행할 코드
}
예시
let count = 0;
while (count < 3) {
console.log(`카운트: ${count}`);
count++;
}
특징
- 반복 횟수가 명확하지 않은 경우 유용
- 초기화 및 증감식을 별도로 관리해야 함
do...while 반복문
최소 1회는 코드를 실행한 후 조건 검사
문법
do {
// 실행 코드
} while (조건);
예시
let userInput;
do {
userInput = prompt("숫자를 입력하세요");
} while (isNaN(userInput));
활용 시나리오
- 사용자 입력 검증
- 조건 검사 전 반드시 1회 실행이 필요한 경우
for...in 반복문
객체의 열거 가능한 속성을 순회할 때 사용
문법
for (변수 in 객체) {
// 실행 코드
}
예시
const person = { name: "Alice", age: 25 };
for (let prop in person) {
console.log(`${prop}: ${person[prop]}`);
}
주의사항
- 배열 인덱스 순회 시 문자열 타입으로 반환
- 상속된 속성까지 포함될 수 있으므로
hasOwnProperty() 검사 필요
for...of 반복문
ES6부터 도입된 이터러블(배열, 문자열, Map 등) 전용 반복문
문법
for (변수 of 이터러블) {
// 실행 코드
}
예시
const fruits = ["사과", "바나나", "체리"];
for (const fruit of fruits) {
console.log(fruit);
}
지원 객체
| 데이터 타입 |
이터러블 여부 |
| Array |
✅ |
| String |
✅ |
| Map |
✅ |
| Set |
✅ |
| DOM 컬렉션 |
✅ |
| 일반 객체 |
❌ |
반복 제어문
break 문
현재 반복문을 즉시 종료합니다.
예시
for (let i = 0; i < 10; i++) {
if (i === 5) break;
console.log(i); // 0~4만 출력
}
continue 문
현재 반복을 건너뛰고 다음 반복으로 진행합니다.
예시
for (let i = 0; i < 5; i++) {
if (i % 2 === 0) continue;
console.log(i); // 홀수(,3)만 출력
}
라벨付き 반복문
중첩 반복문에서 특정 레벨의 반복문을 제어할 수 있습니다.
outerLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i + j === 3) break outerLoop;
console.log(`i=${i}, j=${j}`);
}
}
활용 사례 및 주의사항
일반적인 사용법
-
배열 처리
const numbers = [10, 20, 30];
for (const num of numbers) {
console.log(num * 2);
}
-
객체 속성 검색
const user = { id: 1, role: "admin" };
for (const key in user) {
if (key === "role") {
console.log(`관리자 권한 확인: ${user[key]}`);
break;
}
}
피해야 할 실수
- ❌
for...in으로 배열 인덱스를 숫자 타입으로 기대하는 경우
- ❌ 무한 루프 발생 조건 (
while(true) 등)
- ❌ 이터러블이 아닌 객체에
for...of 사용 시도
성능 고려
- 배열 순회:
for...of가 for 루프보다 약 20% 느린 경우도 있음 (성능 민감한 코드에서 주의)
- DOM 조작:
for...of 대신 querySelectorAll()과 결합한 for 루프 권장
- 최적화 팁: 반복 횟수가 고정된 경우
for 루프가 가장 효율적
재귀를 통한 반복 구현
반복문 외에도 재귀 함수(Recursion)를 통해 동일한 반복 로직을 구현할 수 있습니다. 재귀는 함수가 자기 자신을 다시 호출하여 문제를 더 작은 단위로 쪼개어 해결하는 방식입니다.
- 루프문과의 차이: 루프문이 상태 변수(index 등)를 업데이트하며 반복하는 '반복적(Iterative)' 방식이라면, 재귀는(Call Stack)을 쌓아 처리하는 '선언적' 방식에 가깝습니다.
- 특징: 트리 구조나 그래프 순회와 같이 계층적인 데이터를 처리할 때 루프문보다 훨씬 간결한 코드를 작성할 수 있습니다. 다만, 종료 조건(Base Case)이 없으면 스택 오버플로(Stack Overflow)가 발생하므로 주의가 필요합니다.
for...of 고급 활용법
for...of 문은 배열의 내장 메서드인 entries(), keys(), values()와 결합하여 더 정교한 순회가 가능합니다.
entries(): [인덱스, 값] 형태 반환하여 인덱스와 값을 동시에 추출할 수 있습니다.
const fruits = ["사과", "바나나", "체리"];
for (const [index, fruit] of fruits.entries()) {
console.log(`${index + 1}번째 과일: ${fruit}`);
}
keys(): 요소의 인덱스(키)만 순회합니다.
values(): 요소의 값만 순회합니다 (기본 for...of 동작과 동일).
함수형 반복 메서드
최신 JavaScript에서는 for 루프 대신 고차 함수를 이용한 선언적 반복 처리를 권장합니다. 이는 "어떻게(How)" 반복할 것인가보다 "무엇을(What)" 할 것인가에 집중하는 방식입니다.
주요 메서드 및 예제
forEach: 단순 순회 및 부수 효과(Side Effect) 실행
map: 각 요소를 변환하여 새로운 배열 생성
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2); // [2, 4, 6]
filter: 조건에 맞는 요소만 걸러내어 새로운 배열 생성
const scores = [45, 80, 92, 60];
const passed = scores.filter(s => s >= 80); // [80, 92]
reduce: 누산기를 통해 배열을 하나의 값으로 응축
루프문과의 차이점
| 구분 |
기존 루프 (for, while) |
함수형 메서드 (map, filter 등) |
| 스타일 |
명령형 (Imperative) |
선언적 (Declarative) |
| 상태 관리 |
인덱스 변수 직접 관리 필요 |
내부적으로 처리 (불변성 유지) |
| 제어 |
break, continue 사용 가능 |
중도 중단 불가 (전체 순회) |
비동기 반복 처리
for await...of 구문은 비동기 이터러블(Async Iterable)을 순회할 때 사용하며, 각 반복 단계에서 Promise가 해결(resolve)될 때까지 기다린 후 다음 단계로 진행합니다.
사용 사례: 스트림 데이터 처리
네트워크를 통해 조각나서 들어오는 데이터(ReadableStream)나 비동기 제너레이터를 처리할 때 유용합니다.
async function processStream(stream) {
for await (const chunk of stream) {
console.log(`데이터 수신: ${chunk}`);
// 각 청크가 처리될 때까지 다음 루프로 넘어가지 않음
}
console.log("모든 데이터 처리 완료");
}
Promise.all과의 차이점
Promise.all: 모든 비동기 작업을 동시에(Parallel) 시작하고 모두 끝날 때까지 기다립니다. 속도는 빠르지만, 작업량이 많을 경우 메모리 부하가 큽니다.
for await...of: 비동기 작업을 순차적으로(Sequential) 처리합니다. 이전 작업이 완료되어야 다음 작업을 시작하므로, 순서가 중요하거나 리소스 제한이 있는 경우에 적합합니다.
루프 성능 및 선택 기준
최신 V8 엔진은 for 루프뿐만 아니라 for...of와 고차 함수들에 대해서도 고도의 최적화를 수행하고 있습니다.
성능 벤치마크 (상대적 비교)
환경: Node.js 최신 LTS, 대규모 배열(10^6 요소) 기준
| 루프 방식 |
실행 속도 |
메모리 효율 |
가독성 |
추천 용도 |
for (let i=0...) |
매우 빠름 |
매우 높음 |
낮음 |
극도의 성능 최적화 필요 시 |
for...of |
빠름 |
높음 |
높음 |
일반적인 이터러블 순회 |
forEach |
보통 |
보통 |
높음 |
단순 반복 실행 (Side Effect) |
map / filter |
보통 |
낮음 (새 배열 생성) |
매우 높음 |
데이터 변환 및 가공 |
선택 기준 (Clean Code 관점)
- 데이터 변환이 목적인가? $\rightarrow$
map, filter 사용
- 단순히 모든 요소를 훑어야 하는가? $\rightarrow$
for...of 사용
- 인덱스 제어가 정교하게 필요한가? $\rightarrow$
for 루프 사용
- 비동기 데이터를 순차적으로 처리해야 하는가? $\rightarrow$
for await...of 사용
관련 문서
- MDN Web Docs - 반복문
- ECMAScript 2015+ 문서
- JavaScript 알고리즘 패턴
# 반복문
## 개요
### 반복문이란?
반복문(Loop)은 프로그래밍에서 특정 코드 블록을 **조건이 만족할 때까지 반복 실행**하는 제어 구조입니다. JavaScript에서는 다양한 반복문 구문을 제공하여 배열, 객체, 이터러블(iterable) 등의 데이터를 효율적으로 처리할 수 있도록 지원합니다.
### JavaScript에서의 역할
JavaScript의 반복문은 다음과 같은 상황에서 핵심적인 역할을 합니다:
- 배열/객체의 요소 순회
- 비동기 작업의 반복 처리 (타이머, 네트워크 요청 등)
- 알고리즘 구현 (탐색, 정렬 등)
- 사용자 입력 검증 등 반복적인 작업 자동화
---
## 반복문의 종류
### `for` 반복문
가장 일반적인 반복문 구조로, **초기화 - 조건 검사 - 증감**의 3단계를 포함합니다.
#### 문법
```js
for (초기화; 조건; 증감) {
// 반복 실행할 코드
}
```
#### 예시
```js
for (let i = 0; i < 5; i++) {
console.log(`반복 횟수: ${i}`);
}
```
#### 주의사항
- `var` 대신 `let` 사용 시 블록 스코프로 안전하게 변수 관리 가능
- 무한 루프 방지를 위해 증감식 누락 주의
---
### `while` 반복문
조건이 **참일 동안** 코드 블록을 반복 실행합니다.
#### 문법
```js
while (조건) {
// 반복 실행할 코드
}
```
#### 예시
```js
let count = 0;
while (count < 3) {
console.log(`카운트: ${count}`);
count++;
}
```
#### 특징
- 반복 횟수가 명확하지 않은 경우 유용
- 초기화 및 증감식을 별도로 관리해야 함
---
### `do...while` 반복문
**최소 1회**는 코드를 실행한 후 조건 검사
#### 문법
```js
do {
// 실행 코드
} while (조건);
```
#### 예시
```js
let userInput;
do {
userInput = prompt("숫자를 입력하세요");
} while (isNaN(userInput));
```
#### 활용 시나리오
- 사용자 입력 검증
- 조건 검사 전 반드시 1회 실행이 필요한 경우
---
### `for...in` 반복문
객체의 **열거 가능한 속성**을 순회할 때 사용
#### 문법
```js
for (변수 in 객체) {
// 실행 코드
}
```
#### 예시
```js
const person = { name: "Alice", age: 25 };
for (let prop in person) {
console.log(`${prop}: ${person[prop]}`);
}
```
#### 주의사항
- 배열 인덱스 순회 시 **문자열 타입**으로 반환
- 상속된 속성까지 포함될 수 있으므로 `hasOwnProperty()` 검사 필요
---
### `for...of` 반복문
ES6부터 도입된 이터러블(배열, 문자열, Map 등) 전용 반복문
#### 문법
```js
for (변수 of 이터러블) {
// 실행 코드
}
```
#### 예시
```js
const fruits = ["사과", "바나나", "체리"];
for (const fruit of fruits) {
console.log(fruit);
}
```
#### 지원 객체
| 데이터 타입 | 이터러블 여부 |
|------------------|--------------|
| Array | ✅ |
| String | ✅ |
| Map | ✅ |
| Set | ✅ |
| DOM 컬렉션 | ✅ |
| 일반 객체 | ❌ |
---
## 반복 제어문
### `break` 문
현재 반복문을 즉시 종료합니다.
#### 예시
```js
for (let i = 0; i < 10; i++) {
if (i === 5) break;
console.log(i); // 0~4만 출력
}
```
### `continue` 문
현재 반복을 건너뛰고 다음 반복으로 진행합니다.
#### 예시
```js
for (let i = 0; i < 5; i++) {
if (i % 2 === 0) continue;
console.log(i); // 홀수(,3)만 출력
}
```
### 라벨付き 반복문
중첩 반복문에서 특정 레벨의 반복문을 제어할 수 있습니다.
```js
outerLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i + j === 3) break outerLoop;
console.log(`i=${i}, j=${j}`);
}
}
```
---
## 활용 사례 및 주의사항
### 일반적인 사용법
1. **배열 처리**
```js
const numbers = [10, 20, 30];
for (const num of numbers) {
console.log(num * 2);
}
```
2. **객체 속성 검색**
```js
const user = { id: 1, role: "admin" };
for (const key in user) {
if (key === "role") {
console.log(`관리자 권한 확인: ${user[key]}`);
break;
}
}
```
### 피해야 할 실수
- ❌ `for...in`으로 배열 인덱스를 숫자 타입으로 기대하는 경우
- ❌ 무한 루프 발생 조건 (`while(true)` 등)
- ❌ 이터러블이 아닌 객체에 `for...of` 사용 시도
### 성능 고려
- **배열 순회**: `for...of`가 `for` 루프보다 약 20% 느린 경우도 있음 (성능 민감한 코드에서 주의)
- **DOM 조작**: `for...of` 대신 `querySelectorAll()`과 결합한 `for` 루프 권장
- **최적화 팁**: 반복 횟수가 고정된 경우 `for` 루프가 가장 효율적
---
## 재귀를 통한 반복 구현
반복문 외에도 **재귀 함수(Recursion)**를 통해 동일한 반복 로직을 구현할 수 있습니다. 재귀는 함수가 자기 자신을 다시 호출하여 문제를 더 작은 단위로 쪼개어 해결하는 방식입니다.
- **루프문과의 차이**: 루프문이 상태 변수(index 등)를 업데이트하며 반복하는 '반복적(Iterative)' 방식이라면, 재귀는(Call Stack)을 쌓아 처리하는 '선언적' 방식에 가깝습니다.
- **특징**: 트리 구조나 그래프 순회와 같이 계층적인 데이터를 처리할 때 루프문보다 훨씬 간결한 코드를 작성할 수 있습니다. 다만, 종료 조건(Base Case)이 없으면 스택 오버플로(Stack Overflow)가 발생하므로 주의가 필요합니다.
## for...of 고급 활용법
`for...of` 문은 배열의 내장 메서드인 `entries()`, `keys()`, `values()`와 결합하여 더 정교한 순회가 가능합니다.
- **`entries()`**: `[인덱스, 값]` 형태 반환하여 인덱스와 값을 동시에 추출할 수 있습니다.
```js
const fruits = ["사과", "바나나", "체리"];
for (const [index, fruit] of fruits.entries()) {
console.log(`${index + 1}번째 과일: ${fruit}`);
}
```
- **`keys()`**: 요소의 인덱스(키)만 순회합니다.
- **`values()`**: 요소의 값만 순회합니다 (기본 `for...of` 동작과 동일).
## 함수형 반복 메서드
최신 JavaScript에서는 `for` 루프 대신 고차 함수를 이용한 **선언적 반복 처리**를 권장합니다. 이는 "어떻게(How)" 반복할 것인가보다 "무엇을(What)" 할 것인가에 집중하는 방식입니다.
### 주요 메서드 및 예제
- **`forEach`**: 단순 순회 및 부수 효과(Side Effect) 실행
- **`map`**: 각 요소를 변환하여 **새로운 배열** 생성
```js
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2); // [2, 4, 6]
```
- **`filter`**: 조건에 맞는 요소만 걸러내어 **새로운 배열** 생성
```js
const scores = [45, 80, 92, 60];
const passed = scores.filter(s => s >= 80); // [80, 92]
```
- **`reduce`**: 누산기를 통해 배열을 **하나의 값**으로 응축
### 루프문과의 차이점
| 구분 | 기존 루프 (`for`, `while`) | 함수형 메서드 (`map`, `filter` 등) |
| :--- | :--- | :--- |
| **스타일** | 명령형 (Imperative) | 선언적 (Declarative) |
| **상태 관리** | 인덱스 변수 직접 관리 필요 | 내부적으로 처리 (불변성 유지) |
| **제어** | `break`, `continue` 사용 가능 | 중도 중단 불가 (전체 순회) |
## 비동기 반복 처리
`for await...of` 구문은 **비동기 이터러블(Async Iterable)**을 순회할 때 사용하며, 각 반복 단계에서 `Promise`가 해결(resolve)될 때까지 기다린 후 다음 단계로 진행합니다.
### 사용 사례: 스트림 데이터 처리
네트워크를 통해 조각나서 들어오는 데이터(ReadableStream)나 비동기 제너레이터를 처리할 때 유용합니다.
```js
async function processStream(stream) {
for await (const chunk of stream) {
console.log(`데이터 수신: ${chunk}`);
// 각 청크가 처리될 때까지 다음 루프로 넘어가지 않음
}
console.log("모든 데이터 처리 완료");
}
```
### `Promise.all`과의 차이점
- **`Promise.all`**: 모든 비동기 작업을 **동시에(Parallel)** 시작하고 모두 끝날 때까지 기다립니다. 속도는 빠르지만, 작업량이 많을 경우 메모리 부하가 큽니다.
- **`for await...of`**: 비동기 작업을 **순차적으로(Sequential)** 처리합니다. 이전 작업이 완료되어야 다음 작업을 시작하므로, 순서가 중요하거나 리소스 제한이 있는 경우에 적합합니다.
## 루프 성능 및 선택 기준
최신 V8 엔진은 `for` 루프뿐만 아니라 `for...of`와 고차 함수들에 대해서도 고도의 최적화를 수행하고 있습니다.
### 성능 벤치마크 (상대적 비교)
*환경: Node.js 최신 LTS, 대규모 배열(10^6 요소) 기준*
| 루프 방식 | 실행 속도 | 메모리 효율 | 가독성 | 추천 용도 |
| :--- | :---: | :---: | :---: | :--- |
| `for (let i=0...)` | 매우 빠름 | 매우 높음 | 낮음 | 극도의 성능 최적화 필요 시 |
| `for...of` | 빠름 | 높음 | 높음 | 일반적인 이터러블 순회 |
| `forEach` | 보통 | 보통 | 높음 | 단순 반복 실행 (Side Effect) |
| `map / filter` | 보통 | 낮음 (새 배열 생성) | 매우 높음 | 데이터 변환 및 가공 |
### 선택 기준 (Clean Code 관점)
1. **데이터 변환이 목적인가?** $\rightarrow$ `map`, `filter` 사용
2. **단순히 모든 요소를 훑어야 하는가?** $\rightarrow$ `for...of` 사용
3. **인덱스 제어가 정교하게 필요한가?** $\rightarrow$ `for` 루프 사용
4. **비동기 데이터를 순차적으로 처리해야 하는가?** $\rightarrow$ `for await...of` 사용
## 관련 문서
1. [MDN Web Docs - 반복문](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Loops_and_iteration)
2. [ECMAScript 2015+ 문서](https://www.ecma-international.org/ecma-262/6.0/)
3. [JavaScript 알고리즘 패턴](https://github.com/trekhleb/javascript-algorithms)