junyeokk
Blog
JavaScript·2026. 01. 25

Iterable과 Iterator 프로토콜

개요

for...of 루프로 배열을 순회할 수 있는 이유는 배열이 iterable이기 때문이다. 이 문서에서는 iterable과 iterator의 동작 원리를 살펴본다. 이를 이해하면 커스텀 iterable을 만들거나, matchAll 같은 메서드가 왜 for...of와 함께 쓰이는지 알 수 있다.


Iterable이란

Iterable은 반복 가능한 객체를 말한다. JavaScript에서 for...of 루프로 순회할 수 있으려면 해당 객체가 iterable 프로토콜을 구현해야 한다.

기본적으로 iterable인 객체들이 있다:

  • Array
  • String
  • Map
  • Set
  • TypedArray
  • arguments
  • NodeList

배열과 문자열을 for...of로 순회할 수 있는 이유가 바로 이것이다.

javascript
const arr = [1, 2, 3];
for (const item of arr) {
  console.log(item);
}

위 코드를 실행하면 1, 2, 3이 차례로 출력된다. 배열이 iterable이기 때문에 for...of가 동작한다.

문자열도 마찬가지다:

javascript
const str = "hello";
for (const char of str) {
  console.log(char);
}

문자열의 각 문자가 순회된다. 그런데 이게 어떻게 가능한 걸까?


Iterable 프로토콜의 동작 원리

객체가 iterable이 되려면 Symbol.iterator 메서드를 구현해야 한다. 이 메서드는 iterator 객체를 반환한다.

for...of가 실행되면 내부적으로 다음 과정이 일어난다:

  1. 객체의 [Symbol.iterator]() 메서드를 호출한다
  2. 반환된 iterator 객체의 next() 메서드를 반복 호출한다
  3. next(){ done: true }를 반환하면 순회를 종료한다

직접 iterator를 꺼내서 확인해보자:

javascript
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

for...of는 이 과정을 자동으로 수행한다. 마지막에 done: true가 반환되면 루프가 종료된다.


Iterator 프로토콜

Iterator는 next() 메서드를 가진 객체다. next()는 항상 { value, done } 형태의 객체를 반환해야 한다.

속성설명
value현재 순회 값
done순회 완료 여부. true면 더 이상 값이 없다

이 규칙만 따르면 어떤 객체든 iterable로 만들 수 있다.


커스텀 Iterable 만들기

1부터 3까지 순회하는 커스텀 iterable을 만들어보자:

javascript
const myIterable = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next() {
        count++;
        if (count <= 3) {
          return { value: count, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (const num of myIterable) {
  console.log(num);
}

위 코드를 실행하면 1, 2, 3이 출력된다.

[Symbol.iterator]() 메서드가 iterator 객체를 반환한다. 이 iterator의 next() 메서드가 호출될 때마다 count를 증가시키고, 3을 초과하면 done: true를 반환해서 순회를 종료한다.


Generator로 더 간단하게

Generator 함수를 사용하면 iterator를 더 쉽게 만들 수 있다. yield 키워드가 next() 호출마다 값을 반환하고 일시 정지한다.

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

for (const num of myIterable) {
  console.log(num);
}

앞서 작성한 코드와 동일하게 동작하지만, 코드가 훨씬 간결하다. function* 문법과 yield가 iterator 프로토콜을 자동으로 구현해준다.


Iterable의 다양한 활용

Iterable은 for...of 외에도 여러 곳에서 활용된다.

Spread 연산자

spread 연산자(...)는 iterable을 펼친다:

javascript
const arr = [1, 2, 3];
console.log([...arr]); // [1, 2, 3]

const str = "hello";
console.log([...str]); // ['h', 'e', 'l', 'l', 'o']

내부적으로 Symbol.iterator를 호출해서 모든 값을 꺼낸다.

구조 분해 할당

구조 분해 할당도 iterable 프로토콜을 사용한다:

javascript
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

Array.from

Array.from()은 iterable을 배열로 변환한다:

javascript
const set = new Set([1, 2, 3]);
const arr = Array.from(set);
console.log(arr); // [1, 2, 3]

일반 객체는 Iterable이 아니다

주의할 점이 있다. 일반 객체는 기본적으로 iterable이 아니다.

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

for (const item of obj) {
  console.log(item);
}
// TypeError: obj is not iterable

객체를 순회하려면 Object.keys(), Object.values(), Object.entries()를 사용해야 한다. 이 메서드들은 배열을 반환하고, 배열은 iterable이다.

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

for (const key of Object.keys(obj)) {
  console.log(key);
}
// 'a', 'b'

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value);
}
// 'a' 1
// 'b' 2

정리

  • Iterable은 Symbol.iterator 메서드를 구현한 객체다
  • Iterator는 next() 메서드를 가진 객체이며, { value, done }을 반환한다
  • for...of, spread, 구조 분해 등은 모두 iterable 프로토콜을 사용한다
  • Generator를 사용하면 iterator를 간단하게 구현할 수 있다
  • 일반 객체는 iterable이 아니므로 Object.entries() 등을 활용해야 한다