for...of

for...of

for...of 문은 iterable 객체에서 얻은 값들의 시퀀스를 대상으로 동작하는 루프를 실행합니다. Iterable 객체에는 Array, String, TypedArray, Map, Set, NodeList(및 기타 DOM 컬렉션) 같은 내장 인스턴스뿐만 아니라 arguments 객체, generator 함수가 만든 generator, 사용자 정의 iterable이 포함됩니다.

출처: for...of

본문

for...of 문은 iterable 객체에서 얻은 값들의 시퀀스를 대상으로 동작하는 루프를 실행합니다. Iterable 객체에는 Array, String, TypedArray, Map, Set, NodeList(및 기타 DOM 컬렉션) 같은 내장 인스턴스들뿐만 아니라 arguments 객체, generator 함수가 만든 generator, 사용자 정의 iterable이 포함됩니다.

구문 (Syntax)

for (variable of iterable)
  statement
  • variable: 각 반복에서 시퀀스에서 값을 받습니다. const, let, var, using, await using으로 선언하거나 할당 대상(예: 이전에 선언된 변수, 객체 속성, 구조 분해 패턴)이 될 수 있습니다. var로 선언된 변수는 루프에 국한되지 않아 for...of 루프와 같은 스코프에 있습니다.
  • iterable: iterable 객체입니다. 루프가 동작하는 값 시퀀스의 소스입니다.
  • statement: 모든 반복에서 실행되는 문입니다. variable을 참조할 수 있습니다. 여러 문을 실행하려면 블록 문을 사용하세요.

설명 (Description)

for...of 루프는 iterable에서 얻은 값을 순차적으로 하나씩 처리합니다. 루프가 값에 대해 수행하는 각 동작을 반복(iteration)이라고 하며, 루프가 iterable을 반복한다고 말합니다. 각 반복은 현재 시퀀스 값을 참조할 수 있는 문들을 실행합니다.

for...of 루프가 iterable을 반복할 때, 먼저 iterable의 [Symbol.iterator]() 메서드를 호출해 iterator를 얻고, 그 다음 결과 iterator의 next() 메서드를 반복적으로 호출해 variable에 할당할 값의 시퀀스를 생성합니다.

for...of 루프는 iterator가 완료되면(next() 결과가 done: true인 객체) 종료됩니다. 다른 루핑 문과 마찬가지로 statement 내부에서 제어 흐름 문을 사용할 수 있습니다.

  • break는 문 실행을 중단하고 루프 다음의 첫 번째 문으로 갑니다.
  • continue는 문 실행을 중단하고 루프의 다음 반복으로 갑니다.

for...of 루프가 일찍 종료되면(break 문을 만나거나 오류가 던져지는 경우 등) iterator의 return() 메서드가 호출되어 어떤 정리를 수행합니다.

for...of의 variable 부분은 = 연산자 앞에 올 수 있는 어떤 것이든 허용합니다. 루프 본문 내에서 재할당되지 않는 한 const로 변수를 선언할 수 있습니다(반복 간에 변경될 수 있는데, 이들은 두 개의 별개 변수이기 때문입니다). 그 외에는 let을 사용할 수 있습니다.

const iterable = [10, 20, 30];

for (let value of iterable) {
  value += 1;
  console.log(value);
}
// 11
// 21
// 31

참고: 각 반복은 새 변수를 만듭니다. 루프 본문 내에서 변수를 재할당해도 iterable(이 경우 배열)의 원본 값에는 영향을 주지 않습니다.

using 또는 await using 선언으로 선언된 변수는 루프 반복이 끝날 때마다 폐기됩니다(await using은 반복 끝에 암시적 await를 유발). 그러나 루프가 일찍 종료되면 iterator에 남아 방문되지 않은 값은 폐기되지 않습니다(현재 값은 폐기됨).

const resources = [dbConnection1, dbConnection2, dbConnection3];

for (using dbConnection of resources) {
  dbConnection.query("...");
  // dbConnection is disposed here
}

구조 분해를 사용해 여러 지역 변수를 할당하거나, for (x.y of iterable) 같은 속성 접근자를 사용해 객체 속성에 값을 할당할 수 있습니다. 그러나 변수 이름으로 async를 사용하는 것은 금지하는 특별한 규칙이 있습니다.

let async;
for (async of [1, 2, 3]); // SyntaxError: The left-hand side of a for-of loop may not be 'async'.

이것은 유효한 코드인 for (async of => {};;)(이것은 for 루프)와의 구문 모호성을 피하기 위함입니다. 마찬가지로 using 선언을 사용하면 변수를 of라고 부를 수 없습니다.

for (using of of []); // SyntaxError

예제: Array 반복하기

const iterable = [10, 20, 30];

for (const value of iterable) {
  console.log(value);
}
// 10
// 20
// 30

예제: 문자열 반복하기

문자열은 유니코드 코드 포인트(Unicode code points) 단위로 반복됩니다.

const iterable = "boo";

for (const value of iterable) {
  console.log(value);
}
// "b"
// "o"
// "o"

예제: TypedArray / Map / Set 반복하기

const iterable = new Uint8Array([0x00, 0xff]);

for (const value of iterable) {
  console.log(value);
}
// 0
// 255

Map을 반복하면 각 항목(entry)을 얻을 수 있고, 구조 분해로 키와 값을 따로 얻을 수 있습니다.

const iterable = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3],
]);

for (const entry of iterable) {
  console.log(entry);
}
// ['a', 1]
// ['b', 2]
// ['c', 3]

for (const [key, value] of iterable) {
  console.log(value);
}
// 1
// 2
// 3

Set은 중복을 제거한 값을 반복합니다.

const iterable = new Set([1, 1, 2, 2, 3, 3]);

for (const value of iterable) {
  console.log(value);
}
// 1
// 2
// 3

예제: arguments 객체 / NodeList 반복하기

arguments 객체를 반복해 함수에 전달된 모든 매개변수를 검사할 수 있습니다.

function foo() {
  for (const value of arguments) {
    console.log(value);
  }
}

foo(1, 2, 3);
// 1
// 2
// 3

<article> 요소의 직접 자손인 단락에 read 클래스를 추가하는 예제입니다.

const articleParagraphs = document.querySelectorAll("article > p");
for (const paragraph of articleParagraphs) {
  paragraph.classList.add("read");
}

예제: 사용자 정의 iterable 반복하기

[Symbol.iterator]() 메서드로 사용자 지정 iterator를 반환하는 객체를 반복할 수 있습니다.

const iterable = {
  [Symbol.iterator]() {
    let i = 1;
    return {
      next() {
        if (i <= 3) {
          return { value: i++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};

for (const value of iterable) {
  console.log(value);
}
// 1
// 2
// 3

generator 메서드 [Symbol.iterator]()를 가진 객체를 반복할 수도 있습니다.

const iterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
};

for (const value of iterable) {
  console.log(value);
}
// 1
// 2
// 3

Iterable iterator(this를 반환하는 [Symbol.iterator]() 메서드를 가진 iterator)는 for...of 같은 iterable을 기대하는 구문에서 iterator를 사용 가능하게 하는 꽤 일반적인 기법입니다.

예제: generator 반복하기와 조기 종료

첫 번째 루프에서 break 문을 실행하면 일찍 종료됩니다. iterator는 아직 완료되지 않았으므로 두 번째 루프는 첫 번째 루프가 멈춘 지점부터 계속됩니다. generator는 return() 메서드를 구현하므로 루프가 종료되면 generator 함수가 조기 반환하게 되며, 이는 generator가 루프 간에 재사용되지 않게 합니다.

for...of와 for...in의 차이

for...infor...of 문 모두 무언가를 반복합니다. 주요 차이는 무엇을 반복하느냐입니다. for...in 문은 객체의 열거 가능한 문자열 속성을 반복하는 반면, for...of 문은 iterable 객체가 반복하도록 정의한 값들을 반복합니다.

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

const iterable = [3, 5, 7];
iterable.foo = "hello";

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

for (const i in iterable) {
  if (Object.hasOwn(iterable, i)) {
    console.log(i);
  }
}
// "0" "1" "2" "foo"

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

객체 iterable은 프로토타입 체인에 Object.prototypeArray.prototype을 모두 포함하므로 objCustomarrCustom 속성을 상속받습니다. for...in 루프는 iterable 객체의 열거 가능한 속성만 로그합니다. 배열 요소 3, 5, 7이나 "hello"는 속성이 아니라 값이므로 로그하지 않습니다. 배열 인덱스와 실제 속성인 arrCustom, objCustom은 로그합니다. for...of 루프는 배열(iterable)로서 iterable이 반복하도록 정의한 값을 로그합니다. 객체의 요소 3, 5, 7은 표시되지만 객체의 속성은 하나도 표시되지 않습니다.

더 알아보기