JS

[JS] 반복문 (기초개념)

coco030030 2025. 4. 3. 15:46

 

0. 반복문이란 무엇인가? (기초 개념)

반복문은 같은 코드를 여러 번 실행해야 할 때 사용하는 프로그래밍 도구입니다. 예를 들어, "안녕하세요"를 10번 출력해야 한다면:

// 반복문 없이 작성하면 이렇게 됩니다 (비효율적)
console.log("안녕하세요");
...(중략)
console.log("안녕하세요");

반복문을 사용하면 코드가 간결해집니다:

// 반복문 사용 (효율적)
for(let i = 0; i < 10; i++) {
    console.log("안녕하세요");
}

1. for 반복문 (완전 기초부터)

기본 구조 해부

for(초기화; 조건식; 증감식) {
    // 반복 실행할 코드
}

각 부분의 의미:

  • 초기화: 반복문이 시작될 때 단 한 번만 실행됩니다. 주로 카운터 변수를 선언하고 초기값을 설정합니다.
  • 조건식: 각 반복 시작 전에 평가되며, true이면 반복을 계속, false이면 반복문을 종료합니다.
  • 증감식: 각 반복이 끝날 때마다 실행됩니다. 주로 카운터 변수를 변경합니다.

실행 순서 (초보자가 가장 헷갈리는 부분)

  1. 초기화 실행 (let i = 0)
  2. 조건식 평가 (i < 5가 참인지 확인)
  3. 조건이 참이면 코드 블록 실행
  4. 증감식 실행 (i++)
  5. 2~4 과정을 조건이 거짓이 될 때까지 반복
for(let i = 0; i < 5; i++) {
    console.log(`현재 i 값: ${i}`);
}
// 출력:
// 현재 i 값: 0
// 현재 i 값: 1
// 현재 i 값: 2
// 현재 i 값: 3
// 현재 i 값: 4

초보자가 자주 하는 실수

// 실수 1: 세미콜론(;) 대신 쉼표(,) 사용
for(let i = 0, i < 5, i++) { } // 오류!

// 실수 2: 무한 루프 (조건이 항상 참)
for(let i = 0; i > -1; i++) { } // 무한 반복!

// 실수 3: 변수 범위 오해
for(let i = 0; i < 5; i++) {
    // i는 여기서만 사용 가능
}
console.log(i); // 오류! i는 정의되지 않음

// 실수 4: 증감식 누락
for(let i = 0; i < 5;) {
    console.log(i); // 무한히 0만 출력
}

2. while 반복문 (상세 설명)

기본 구조 해부

초기화; // 반복문 외부에서 초기화
while(조건식) {
    // 반복 실행할 코드
    증감식 또는 조건 변경 코드; // ⚠️ 이걸 잊으면 무한 루프
}

for문과 while문의 변환 (이해를 위한 비교)

같은 기능을 하는 for문과 while문:

// for문
for(let i = 0; i < 5; i++) {
    console.log(i);
}

// 동일한 while문
let i = 0;           // 초기화가 밖으로
while(i < 5) {       // 조건식만 남음
    console.log(i);
    i++;             // 증감식이 코드 블록 안으로
}

while문이 유리한 상황

// 예: 사용자가 올바른 입력을 할 때까지 반복
let userInput = "";
while(userInput !== "quit") {
    userInput = prompt("명령을 입력하세요 (종료하려면 'quit'): ");
    console.log(`입력받은 명령: ${userInput}`);
}

// 예: 난수가 특정 값이 될 때까지 반복
let randomNum;
while(randomNum !== 10) {
    randomNum = Math.floor(Math.random() * 100) + 1;
    console.log(`생성된 난수: ${randomNum}`);
}

3. do-while 반복문 (초보자가 잘 모르는 부분)

while과 do-while의 핵심 차이

do-while은 조건을 나중에 검사하므로 코드 블록이 최소 1번은 반드시 실행됩니다.

// while: 조건이 처음부터 거짓이면 한 번도 실행되지 않음
let a = 10;
while(a < 5) {
    console.log("while: " + a); // 실행 안 됨
    a++;
}

// do-while: 조건이 거짓이어도 최소 한 번은 실행됨
let b = 10;
do {
    console.log("do-while: " + b); // "do-while: 10" 출력
    b++;
} while(b < 5);

실제 사용 사례 (더 구체적인 예시)

// 예: 메뉴 시스템
let choice;
do {
    console.log("\n1. 새 게임");
    console.log("2. 계속하기");
    console.log("3. 설정");
    console.log("4. 종료");
    choice = prompt("메뉴를 선택하세요 (1-4): ");

    // 선택한 메뉴 처리
    switch(choice) {
        case "1": console.log("새 게임을 시작합니다..."); break;
        case "2": console.log("저장된 게임을 불러옵니다..."); break;
        case "3": console.log("설정 메뉴로 이동합니다..."); break;
        case "4": console.log("게임을 종료합니다..."); break;
        default: console.log("잘못된 선택입니다. 다시 시도하세요.");
    }
} while(choice !== "4");

4. break와 continue (흐름 제어의 핵심)

break 자세히 살펴보기

break는 현재 반복문을 즉시 종료하고 다음 코드로 넘어갑니다.

// 예: 배열에서 특정 값 찾기
const numbers = [4, 8, 15, 16, 23, 42];
let foundIndex = -1;

for(let i = 0; i < numbers.length; i++) {
    if(numbers[i] === 15) {
        foundIndex = i;
        break; // 찾으면 더 이상 반복할 필요 없음
    }
}

console.log(`15는 인덱스 ${foundIndex}에 있습니다.`);

// 중첩 반복문에서의 break (주의: 가장 안쪽 반복문만 종료)
for(let i = 0; i < 3; i++) {
    console.log(`바깥 반복: ${i}`);
    for(let j = 0; j < 3; j++) {
        if(j === 1) break; // 내부 반복문만 종료
        console.log(`  내부 반복: ${j}`);
    }
}

continue 자세히 살펴보기

continue는 현재 반복을 건너뛰고 다음 반복으로 바로 이동합니다.

// 예: 짝수만 처리하기
for(let i = 0; i < 10; i++) {
    if(i % 2 !== 0) {
        continue; // 홀수면 아래 코드 실행하지 않고 다음 반복으로
    }
    console.log(`짝수 처리: ${i}`);
}

// 예: 특수한 값 건너뛰기
const scores = [85, -1, 92, -1, 78, 90]; // -1은 결시
let sum = 0;
let count = 0;

for(let i = 0; i < scores.length; i++) {
    if(scores[i] === -1) {
        continue; // 결시는 평균 계산에서 제외
    }
    sum += scores[i];
    count++;
}

const average = sum / count;
console.log(`평균 점수: ${average}`); // 결시를 제외한 평균

5. for-in 반복문 (객체 탐색의 열쇠)

객체의 구조와 for-in의 관계

자바스크립트 객체는 키:값 쌍으로 구성됩니다. for-in은 이 를 하나씩 가져옵니다.

// 객체 정의
const student = {
    name: "홍길동",
    age: 23,
    major: "컴퓨터공학",
    gpa: 3.5
};

// for-in으로 모든 속성 접근
for(let key in student) {
    console.log(`${key}: ${student[key]}`);
}
// 출력:
// name: 홍길동
// age: 23
// major: 컴퓨터공학
// gpa: 3.5

// ⚠️ 주의: student.key는 작동하지 않음 (undefined)
for(let key in student) {
    console.log(`${key}: ${student.key}`); // 잘못된 방법!
}

상속된 속성과 hasOwnProperty

// 객체 상속 예시
const person = {
    isHuman: true
};

const student = Object.create(person);
student.name = "홍길동";
student.age = 23;

// for-in은 상속된 속성도 순회함
for(let key in student) {
    console.log(`${key}: ${student[key]}`);
}
// 출력:
// name: 홍길동
// age: 23
// isHuman: true (상속된 속성)

// 자체 속성만 확인하려면 hasOwnProperty 사용
for(let key in student) {
    if(student.hasOwnProperty(key)) {
        console.log(`자체 속성 ${key}: ${student[key]}`);
    }
}
// 출력:
// 자체 속성 name: 홍길동
// 자체 속성 age: 23

6. for-of 반복문 (ES6의 강력한 도구)

이터러블(Iterable) 객체란?

for-of이터러블 객체의 을 하나씩 가져옵니다. 이터러블은 반복 가능한 객체로, 배열, 문자열, Map, Set 등이 해당됩니다.

// 배열에서 사용
const colors = ["빨강", "파랑", "초록"];
for(let color of colors) {
    console.log(color);
}
// 출력: 빨강, 파랑, 초록

// 문자열에서 사용 (각 글자를 순회)
const message = "Hello";
for(let char of message) {
    console.log(char);
}
// 출력: H, e, l, l, o

// Map에서 사용
const userRoles = new Map([
    ["john", "admin"],
    ["jane", "editor"],
    ["bob", "user"]
]);

for(let entry of userRoles) {
    console.log(entry); // [키, 값] 배열 출력
}

// Map 구조 분해 할당으로 더 깔끔하게
for(let [user, role] of userRoles) {
    console.log(`${user}의 역할: ${role}`);
}

for-in과 for-of의 차이점 (초보자가 헷갈리는 부분)

const arr = ["a", "b", "c"];
arr.customProp = "test"; // 배열에 사용자 정의 속성 추가

// for-in: 인덱스와 추가 속성을 순회
for(let key in arr) {
    console.log(`${key}: ${arr[key]}`);
}
// 출력:
// 0: a
// 1: b
// 2: c
// customProp: test

// for-of: 값만 순회 (사용자 정의 속성 무시)
for(let value of arr) {
    console.log(value);
}
// 출력:
// a
// b
// c

7. 중첩 반복문 (초보자가 어려워하는 개념)

기본 개념과 작동 방식

중첩 반복문은 반복문 안에 또 다른 반복문이 있는 구조입니다. 바깥쪽 반복문이 한 번 실행될 때마다 안쪽 반복문은 전체를 다 실행합니다.

// 구구단 출력 예제
for(let i = 2; i <= 9; i++) {
    console.log(`--- ${i}단 ---`);
    for(let j = 1; j <= 9; j++) {
        console.log(`${i} x ${j} = ${i * j}`);
    }
    console.log(""); // 단 사이 빈 줄
}

실행 횟수 이해하기

외부 반복이 n번, 내부 반복이 m번이면 전체 코드 블록은 n×m번 실행됩니다.

let counter = 0;
for(let i = 0; i < 3; i++) {
    for(let j = 0; j < 4; j++) {
        counter++;
        console.log(`실행 ${counter}: i=${i}, j=${j}`);
    }
}
console.log(`총 실행 횟수: ${counter}`); // 12 (3×4)

실용적인 예제: 2D 배열 처리

// 2D 배열 (행렬) 선언
const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// 행렬의 모든 요소 합계 구하기
let sum = 0;
for(let i = 0; i < matrix.length; i++) {
    for(let j = 0; j < matrix[i].length; j++) {
        sum += matrix[i][j];
    }
}
console.log(`행렬 요소의 합: ${sum}`); // 45

// for-of를 사용한 더 간결한 버전
sum = 0;
for(let row of matrix) {
    for(let cell of row) {
        sum += cell;
    }
}
console.log(`행렬 요소의 합 (for-of 사용): ${sum}`); // 45

8. 반복문 선택 가이드 (초보자를 위한 의사결정 트리)

어떤 반복문을 선택해야 할지 고민될 때 아래 질문에 순서대로 답해보세요:

  1. 반복 횟수가 미리 정해져 있나요?
    • 예 → for 반복문 사용 (예: 배열의 모든 요소 순회)
    • 아니오 → 다음 질문으로
  2. 조건을 먼저 확인해야 하나요?
    • 예 → while 반복문 사용 (예: 사용자가 특정 입력을 할 때까지)
    • 아니오 (최소 한 번은 실행해야 함) → do-while 사용 (예: 메뉴 표시)
  3. 객체의 속성을 순회해야 하나요?
    • 예 → for-in 반복문 사용
  4. 이터러블 객체(배열, 문자열 등)의 값을 직접 가져와야 하나요?
    • 예 → for-of 반복문 사용
// 각 상황별 적절한 반복문 예시

// 1. 정확한 반복 횟수가 있을 때: for
const scores = [85, 92, 78, 90, 88];
for(let i = 0; i < scores.length; i++) {
    console.log(`학생 ${i+1}의 점수: ${scores[i]}`);
}

// 2-a. 조건에 따라 반복할 때: while
let balance = 1000;
let month = 0;
while(balance < 2000) {
    balance *= 1.05; // 매월 5% 이자
    month++;
}
console.log(`${month}개월 후 잔액이 2000을 넘습니다: ${balance.toFixed(2)}`);

// 2-b. 최소 한 번은 실행해야 할 때: do-while
let pinCode;
do {
    pinCode = prompt("4자리 PIN 코드를 입력하세요: ");
} while(pinCode.length !== 4 || isNaN(pinCode));

// 3. 객체 속성 순회: for-in
const car = {
    model: "테슬라 모델 3",
    year: 2023,
    color: "빨강",
    electric: true
};
for(let prop in car) {
    console.log(`${prop}: ${car[prop]}`);
}

// 4. 이터러블 값 직접 접근: for-of
const fruits = ["사과", "바나나", "체리"];
for(let fruit of fruits) {
    console.log(`과일: ${fruit}`);
}

9. 디버깅 팁 (초보자가 반드시 알아야 할 내용)

무한 루프 방지 및 해결

// 무한 루프가 발생하는 흔한 실수들

// 실수 1: 증감식 누락
for(let i = 0; i < 10; ) {
    console.log(i);
    // i++; <- 이 부분이 누락됨
}

// 실수 2: 조건이 절대 거짓이 되지 않음
let count = 0;
while(count < 10) {
    console.log(count);
    count--; // 증가가 아닌 감소 (count는 계속 작아짐)
}

// 실수 3: 조건 부등호 방향 오류
for(let i = 0; i > -5; i++) {
    console.log(i); // i는 증가하므로 절대 -5보다 작아지지 않음
}

// 해결책: 항상 반복문에 탈출 조건을 확실히 만들고,
// 코드를 작성할 때 반복 횟수 제한을 둬보세요
let safetyCounter = 0;
while(condition) {
    // 코드...
    safetyCounter++;
    if(safetyCounter > 1000) {
        console.error("안전 제한에 도달: 가능한 무한 루프");
        break;
    }
}

console.log를 활용한 디버깅

// 반복문 내부 상태 추적하기
for(let i = 0; i < 5; i++) {
    console.log(`--- 반복 ${i} 시작 ---`);
    // 변수 상태 확인
    console.log("현재 i 값:", i);

    // 계산 과정 추적
    let result = i * 2;
    console.log(`${i} * 2 = ${result}`);

    console.log(`--- 반복 ${i} 종료 ---\n`);
}

10. 실전 예제 모음 (개념 통합하기)

1) 배열에서 특정 조건의 요소만 찾기

const numbers = [15, 32, 7, 84, 56, 23, 9, 41];
const result = [];

// 20보다 크고 50보다 작은 숫자만 필터링
for(let i = 0; i < numbers.length; i++) {
    if(numbers[i] > 20 && numbers[i] < 50) {
        result.push(numbers[i]);
    }
}

console.log("필터링된 숫자:", result); // [32, 41]

2) 구구단 출력 (중첩 반복문)

// 사용자가 입력한 단만 출력
const dan = parseInt(prompt("출력할 구구단의 단을 입력하세요 (2~9): "));

if(dan >= 2 && dan <= 9) {
    console.log(`--- ${dan}단 ---`);
    for(let i = 1; i <= 9; i++) {
        console.log(`${dan} x ${i} = ${dan * i}`);
    }
} else {
    console.log("2에서 9 사이의 숫자를 입력해주세요.");
}

3) 소수(Prime Number) 찾기

// 1부터 100까지의 모든 소수 찾기
const primes = [];

// 2부터 100까지의 각 수에 대해
for(let num = 2; num <= 100; num++) {
    let isPrime = true;

    // 2부터 num의 제곱근까지의 수로 나누어 보기
    for(let i = 2; i <= Math.sqrt(num); i++) {
        if(num % i === 0) {
            // 나누어 떨어지면 소수가 아님
            isPrime = false;
            break; // 더 이상 확인할 필요 없음
        }
    }

    // 소수라면 배열에 추가
    if(isPrime) {
        primes.push(num);
    }
}

console.log("1부터 100까지의 소수:", primes);

4) 삼각형 별 패턴 그리기

// 삼각형 패턴 출력하기
const height = 5;
let pattern = "";

// 정상 삼각형
console.log("정상 삼각형:");
for(let i = 1; i <= height; i++) {
    pattern = "";
    for(let j = 1; j <= i; j++) {
        pattern += "* ";
    }
    console.log(pattern);
}
// 출력:
// * 
// * * 
// * * * 
// * * * * 
// * * * * * 

// 역삼각형
console.log("\n역삼각형:");
for(let i = height; i >= 1; i--) {
    pattern = "";
    for(let j = 1; j <= i; j++) {
        pattern += "* ";
    }
    console.log(pattern);
}

결론

자바스크립트 반복문은 기초지만 매우 강력한 도구입니다. 초보자는 다음 순서로 학습하는 것이 효과적입니다:

  1. 기본 for문의 구조와 작동 원리 완전히 이해하기
  2. while과 do-while의 차이점 파악하기
  3. break와 continue의 흐름 제어 연습하기
  4. 객체와 배열 순회를 위한 for-in, for-of 활용하기
  5. 중첩 반복문으로 복잡한 패턴 만들기
  6. 실제 문제 해결에 반복문 적용해보기

가장 중요한 것은 개념을 단순히 읽는 것이 아니라, 직접 코드를 작성하고 다양하게 변형해보면서 실험하는 것입니다. 코드가 작동하는 이유와 작동하지 않는 이유를 이해하는 과정에서 진정한 학습이 이루어집니다.