Skip to content
This page has been auto-translated and may contain errors.View in English

반복문

docs.scrimba.com

1부터 100까지 숫자를 출력하거나, 단어 속 모든 글자에 인사를 건네거나, 정답이 나올 때까지 계속 질문을 던지고 싶다고 해봅시다. 같은 줄을 백 번 써도 되지만, 입력하기도 번거롭고 나중에 수정하기는 더 번거롭습니다. 반복문은 같은 코드를 여러 번 실행하도록 자바스크립트에 지시하는 방법으로, 작업 코드는 한 번만 작성하고 반복은 언어가 대신 처리하게 해줍니다. 이 장에서는 가장 많이 쓰는 세 가지 반복문인 for, while, for...of를 다룹니다.

for 반복문

**for 반복문**은 몇 번 반복할지 알고 있을 때 사용합니다. 횟수를 대신 세어줍니다. 다음은 0부터 4까지 숫자를 출력하는 예입니다:

js
for (let i = 0; i < 5; i++) {
  console.log(i);
}
// 0
// 1
// 2
// 3
// 4

괄호 안 부분은 세미콜론으로 구분된 세 부분으로 이루어져 있습니다:

  • let i = 0시작입니다: i라는 이름의 카운터를 만들고 0으로 설정합니다.
  • i < 5조건입니다: 이 조건이 참인 동안 반복문이 계속 실행됩니다.
  • i++단계입니다: 매번 실행이 끝날 때마다 i에 1을 더합니다(i++i = i + 1을 줄인 표현입니다).

즉, 반복문은 i를 0에서 시작하고, i < 5인지 확인한 다음, 중괄호 안의 코드를 실행하고, 1을 더한 뒤 다시 확인합니다. i가 5에 도달하면 조건이 거짓이 되어 반복문이 멈춥니다.

**for 반복문**은 반복 횟수를 미리 알고 있는 상황을 위해 만들어졌습니다. 헤더 안에 세 부분을 순서대로 담고 있습니다:

js
for (let i = 0; i < 5; i++) {
  console.log(`pass number ${i}`);
}
// pass number 0
// pass number 1
// pass number 2
// pass number 3
// pass number 4

헤더를 왼쪽부터 읽어보면, let i = 0은 다른 모든 것보다 먼저 한 번만 실행되는 초기화식입니다. i < 5는 매 실행 전에 확인되는 조건이며, 이 조건이 참인 동안에만 반복문이 실행됩니다. i++는 매 실행이 끝난 뒤 실행되는 단계입니다. 카운터 i는 일반 변수이므로 반복문 안에서 읽을 수 있고, 이 점 때문에 반복문과 카운트가 함께 다뤄지는 것입니다.

카운터를 0부터 시작하는 것은 규칙이 아니라 관례입니다. 0부터 세는 방식은 자바스크립트 거의 모든 곳에서 볼 수 있고, 배열이 항목 번호를 매기는 방식과도 맞아떨어지므로 일찍부터 익숙해질 필요가 있습니다.

**for 반복문**은 정해진 횟수만큼 반복하는 전체 구조를 초기화식, 조건, 단계 세 부분을 세미콜론으로 구분한 하나의 헤더에 담아냅니다.

js
for (let i = 0; i < 5; i++) {
  console.log(`pass number ${i}`);
}
// pass number 0
// pass number 1
// pass number 2
// pass number 3
// pass number 4

이 순서는 정확하며 꼭 기억해둘 만합니다: 초기화식이 한 번 실행되고, 조건이 검사되며, 조건이 참일 때만 본문이 실행된 다음, 단계가 실행되고 다시 조건으로 돌아갑니다. 이 순서 때문에 조건은 첫 실행 전에 먼저 검사되며, 처음부터 거짓인 조건을 가진 반복문은 본문을 한 번도 실행하지 않습니다.

카운터에는 const가 아니라 let을 사용하세요. 단계에서 매번 카운터 값을 다시 할당하기 때문입니다. 그리고 i를 헤더에서 선언하면 반복문 범위로 스코프가 제한되어 주변 코드로 새어나가지 않으므로, 반복문을 중첩하거나 여러 개를 나란히 작성할 때 카운터가 서로 충돌하는 일을 막아줍니다. 1이 아니라 0부터 세는 것은 거의 보편적인 관례이며, 배열이 요소에 인덱스를 매기는 방식과 그대로 맞아떨어지므로 데이터를 순회하는 순간부터 이 습관이 도움이 됩니다.

Junofor 반복문for 반복문은 정해진 횟수만큼 반복하며 횟수를 대신 세어줍니다. 헤더는 세 부분으로 이루어져 있는데, let i = 0 같은 시작, 반복을 계속하게 하는 i < 5 같은 조건, 진행시키는 i++ 같은 단계입니다. 조건이 거짓이 되면 반복문이 멈춥니다.
Junofor 반복문 반복 횟수를 미리 알고 있다면 for를 사용하세요. 헤더는 초기화식을 한 번 실행하고, 매 실행 전에 조건을 확인하며, 매 실행 후에 단계를 실행합니다. 0부터 세는 것이 관례이며 배열이 항목 번호를 매기는 방식과도 맞아떨어지니 지금부터 익숙해지세요.
Junofor 반복문 헤더의 순서는 고정되어 있습니다: 한 번 초기화하고, 조건을 검사하고, 본문을 실행하고, 단계를 실행하고, 다시 반복합니다. 그래서 처음부터 거짓인 조건은 본문을 완전히 건너뜁니다. 단계에서 다시 할당할 수 있도록 카운터에는 let을 사용하고, 반복문 범위에만 스코프가 제한되도록 헤더 안에서 선언하세요.

while 반복문

때로는 몇 번 반복할지 모르고, 참으로 유지되어야 하는 조건만 알고 있는 경우가 있습니다. 그럴 때 쓰는 것이 **while 반복문**입니다. 조건이 유지되는 동안 계속 실행됩니다:

js
let count = 3;
while (count > 0) {
  console.log(count);
  count = count - 1;
}
console.log("Go");
// 3
// 2
// 1
// Go

반복문은 count > 0을 확인하고, 코드를 실행한 다음, 다시 확인합니다. 반복문 안에서 무언가가 count를 바꾸고 있다는 점에 주목하세요. 그래야 조건이 결국 거짓이 되어 반복문이 멈춥니다. 반복문 안의 무언가가 조건을 거짓으로 만들지 않으면 반복문은 영원히 실행됩니다. 이것이 무한 루프이며, 프로그램을 멈춰버리게 하므로 조건이 바뀔 수 있도록 항상 신경 써야 합니다.

**while 반복문**은 조건이 참으로 유지되는 동안 반복하며, 내장된 카운터나 단계를 갖지 않습니다. 그래서 반복 횟수를 미리 알 수 없을 때는 while이 적절한 도구이고, 반복 횟수를 미리 알 때는 for 반복문이 적절한 도구입니다.

js
let attempts = 0;
let password = "";
while (password !== "letmein") {
  password = "letmein"; // 실제 입력을 대신하는 자리 표시
  attempts = attempts + 1;
}
console.log(`Unlocked after ${attempts} attempt(s)`);
// Unlocked after 1 attempt(s)

이 조건(password !== "letmein")은 조건문에서 배운 비교 연산자를 활용하며, 이 조건이 거짓이 될 때까지 반복문이 실행됩니다. 문제는 조건이 거짓이 되도록 강제하는 것이 아무것도 없다는 점입니다: 조건이 참조하는 무언가를 반복문 안에서 직접 바꿔야 합니다. 조건이 결코 거짓이 되지 않는 while 반복문은 영원히 실행됩니다. 이것이 무한 루프이며, 프로그램을 멈춰버리게 합니다.

**while 반복문**은 일반적인 형태입니다: 조건이 참인 동안 반복하며, 카운트가 필요하다면 그것을 전적으로 여러분에게 맡깁니다. 반복 횟수가 정해져 있지 않을 때, 예를 들어 입력이 유효할 때까지 반복하거나 값이 어떤 임계값을 넘을 때까지 반복하는 경우에 사용하세요. 반복 횟수를 알고 있다면 for 반복문이 그 의도를 더 명확하게 드러냅니다.

js
let remaining = 100;
let steps = 0;
while (remaining > 0) {
  remaining = remaining - 15;
  steps = steps + 1;
}
console.log(`Emptied in ${steps} steps`);
// Emptied in 7 steps

주의해야 할 실패 유형은 무한 루프입니다: while은 종료를 구조적으로 보장하지 않으므로, 본문이 조건을 거짓 쪽으로 밀어붙여야 합니다. 반복문이 멈추지 않게 만드는 원인은 세 가지입니다: 조건이 전혀 업데이트되지 않는 경우, 업데이트가 잘못된 방향으로 진행되는 경우, 그리고 조건이 정확히 맞아떨어지지 않고 건너뛰어질 수 있는 경우입니다(위 예에서 remaining === 0을 사용했다면 놓쳤을 것입니다. remaining이 정확히 0이 아니라 음수 값에 도달하기 때문입니다). 모든 while 반복문에는 조건을 거짓 쪽으로 밀어붙이는 줄이 필요합니다. 정확히 === 0인지 확인하기보다 > 0 같은 범위 검사를 사용하면, 단계가 목표를 지나쳐도 반복문이 여전히 끝날 수 있습니다.

Junowhile 반복문while 반복문은 조건이 참인 동안 반복하며, 내장된 카운터가 없습니다. 반복 횟수를 미리 알 수 없을 때 사용하세요. 조건이 거짓이 될 수 있도록 반복문 안에서 항상 무언가를 바꿔야 합니다. 그러지 않으면 프로그램을 멈춰버리는 무한 루프가 생깁니다.
Junowhile 반복문 반복 횟수를 모를 때는 while을, 정해져 있을 때는 for를 선택하세요. 본문은 조건이 검사하는 대상을 바꿔야 합니다. 그러지 않으면 조건이 절대 거짓이 되지 않습니다. 이것이 무한 루프이며, while을 작성할 때마다 주의해야 할 실수입니다.
Junowhile 반복문while은 언젠가 멈춘다는 보장을 전혀 해주지 않으므로, 본문이 조건을 거짓 쪽으로 밀어붙여야 합니다. 멈추지 않게 되는 세 가지 경우: 조건이 전혀 업데이트되지 않거나, 잘못된 방향으로 움직이거나, 건너뛰어지는 경우입니다. 정확히 === 0인지 확인하기보다 > 0 같은 범위 검사를 사용하면 목표를 지나치는 단계라도 반복문이 끝날 수 있습니다.

for...of로 문자열 순회하기

forwhile 반복문은 숫자로 횟수를 세지만, 어떤 대상 안의 각 항목을 직접 방문하고 싶을 때가 많습니다. **for...of**가 바로 그런 일을 합니다. 다음은 단어의 각 글자를 순회하는 예입니다:

js
for (const char of "hello") {
  console.log(char);
}
// h
// e
// l
// l
// o

"단어의 각 글자에 대해"라고 읽으면 됩니다. 매 실행마다 char에는 다음 글자가 들어가며, 카운터나 조건을 전혀 다룰 필요가 없습니다. 모든 항목을 순서대로 살펴보는 것이 목적이라면 for 반복문보다 더 깔끔합니다.

for...of는 대부분 다음 장들에서 만날, 값들의 목록인 배열에서 사용하게 됩니다. 문자열은 처음 순회해보기에 좋은 대상인데, 이미 글자들의 나열이기 때문이고, for...of도 문자열을 정확히 그렇게 다룹니다.

**for...of**는 시퀀스의 항목들을 직접 순회하며, 카운터나 관리해야 할 조건 없이 매번 각 값을 건네줍니다:

js
let vowels = 0;
for (const char of "javascript") {
  if (char === "a" || char === "i") {
    vowels = vowels + 1;
  }
}
console.log(`Found ${vowels} of those vowels`);
// Found 3 of those vowels

여기서 const char를 사용한 것은 의도적입니다: 매 실행마다 새로운 char가 만들어지고, 이를 읽기만 할 뿐 다시 할당하지 않으므로 const가 적합하며 그 의도를 잘 나타냅니다. for...of 반복문은 카운터 반복문의 수동 관리 작업을 가독성과 맞바꾸며, 직접 인덱스를 다루지 않으므로 off-by-one 오류 부류를 통째로 없애줍니다.

문자열은 처음 순회해보기에 자연스러운 대상이지만, 진짜 주력으로 쓰이는 곳은 배열입니다. 배열을 배우고 나면 for...of가 그 내용을 순회하는 깔끔하고 직접적인 방법이 됩니다.

**for...of**는 어떤 시퀀스든 요소 단위로 순회하며, 인덱스를 드러내지 않고 각 값을 이름에 바인딩합니다. 문자열에서는 글자를 하나씩 내놓습니다:

js
let shout = "";
for (const char of "loops") {
  shout = shout + char.toUpperCase();
}
console.log(shout);
// LOOPS

반복문 변수는 const로 바인딩하세요: for...of는 매 실행마다 새로운 바인딩을 만들기 때문에 다시 할당할 필요가 없고, const는 그 값이 본문 안에서 읽기 전용임을 잘 드러냅니다. 카운터 반복문보다 얻는 이점은 인덱스를 전혀 작성하지 않는다는 점이며, 이는 구조적으로 off-by-one 오류를 피하게 해줍니다.

이것이 가장 중요하게 쓰이는 곳은 배열입니다. 각 요소를 방문하는 것이 목적이라면 for...of가 카운트 방식의 for 반복문보다 더 깔끔하게 읽히고 의도를 곧바로 드러냅니다. 하지만 컬렉션을 새로운 컬렉션으로 변환하는 작업이라면, 배열 순회 메서드가 대부분 수동 반복문을 완전히 대체하며, 이는 별도의 장에서 다룹니다.

break와 continue

두 가지 키워드는 어떤 반복문에서든 더 세밀한 제어를 제공합니다. break는 반복문을 즉시 멈추고 그 다음 코드로 넘어갑니다. continue는 현재 실행의 나머지를 건너뛰고 바로 다음 실행으로 넘어갑니다.

js
for (let i = 0; i < 10; i++) {
  if (i === 3) {
    continue; // 3은 건너뛰고 계속 반복
  }
  if (i === 6) {
    break; // 반복문을 완전히 멈춤
  }
  console.log(i);
}
// 0
// 1
// 2
// 4
// 5

break는 필요한 것을 찾은 뒤 일찍 멈추어 무의미하게 계속 훑지 않게 해주는 방법입니다. continue는 관심 없는 경우를 나머지 본문 전체를 if로 감싸지 않고 건너뛰는 방법입니다.

off-by-one 오류와 인덱스 범위

전형적인 반복문 버그는 off-by-one 오류입니다: 한 번 더 실행하거나 한 번 덜 실행하는 것입니다. 이는 카운터의 범위에서 생겨납니다. 길이가 5인 문자열은 0, 1, 2, 3, 4 위치에 글자를 가지고 있으므로, 마지막으로 유효한 위치는 length - 1이지 length가 아닙니다. 그래서 위치를 세는 반복문은 i <= text.length가 아니라 i < text.length를 사용합니다:

js
const text = "hello";
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// h
// e
// l
// l
// o

i <= text.length로 작성하면 마지막 실행이 존재하지 않는 5번 위치에 도달하여 undefined를 만들어냅니다. 0부터 세면서 <로 길이까지 반복하는 패턴이 정확히 모든 위치에만 딱 들어맞는 방식입니다. 각 항목만 필요하고 위치는 필요 없다면, for...of는 이런 범위 문제를 완전히 피할 수 있으므로 선호할 이유가 됩니다.

Junofor...of로 문자열 순회하기for...of는 카운터나 관리해야 할 조건 없이 각 항목을 직접 방문합니다. 문자열에서는 매 실행마다 다음 글자를 건네줍니다. 나중에 배열에서 가장 많이 사용하겠지만, 문자열은 이미 글자들의 나열이므로 처음 순회해보기에 깔끔한 대상입니다.
Junofor...of로 문자열 순회하기for...of는 매번 각 값을 건네주므로, 직접 인덱스를 다룰 때 생기는 off-by-one 위험과 카운터를 함께 건너뛸 수 있습니다. 매 실행이 읽기 전용이므로 const로 바인딩하세요. 문자열은 좋은 출발점이지만, 배열을 배우고 나면 바로 그곳에서 자주 사용하게 될 것입니다.
Junofor...of로 문자열 순회하기for...of는 각 요소를 새로운 const에 바인딩하고 인덱스를 전혀 드러내지 않아 구조적으로 off-by-one 버그를 없애줍니다. break는 일찍 멈추고, continue는 한 번의 실행을 건너뜁니다. 컬렉션을 순회하는 것뿐 아니라 변환하는 작업이라면, 배열 순회 메서드가 대부분 수동 반복문을 대체하며 별도의 장에서 다룹니다.

반복문, 그 다음은

반복문은 같은 코드를 반복해서 쓰지 않고 언어가 대신 반복을 처리하게 해주는 방법입니다. for 반복문은 정해진 횟수만큼 세고, while은 조건이 유지되는 동안 반복하며, for...of는 시퀀스의 항목들을 직접 순회합니다. 여기서 꼭 가져가야 할 습관 하나는, 모든 반복문이 반드시 끝날 수 있도록 하고, 각 항목만 필요하다면 for...of를 선호하는 것입니다.

반복문이 정말 중요한 이유는 여러분이 다루게 될 데이터 대부분이 컬렉션 형태로 온다는 점입니다. 다음 장들에서는 여러분이 끊임없이 순회하게 될 목록인 배열을 소개하고, 이어서 데이터를 변환할 때 수동으로 작성한 반복문을 대신 대체하곤 하는 배열 메서드를 다룹니다. 반복문이 의존하는 조건을 더 다듬고 싶다면, 조건문에서 반복문이 계속될지 멈출지를 결정하는 비교 방법을 다룹니다.