27. Iterable

다양한 데이터 구조를 공통된 반복 방식으로 순회할 수 있도록 해주는 프로토콜

Index

Iterable & Iterator 프로토콜

for...of vs for...in

유사 배열 vs 이터러블 vs DOM 특수 객체

이터레이터 vs 이터러블 vs 둘 다 되는 객체


1. Iterable & Iterate

1.1 이터러블 프로토콜(Iterable protocol)

정의: 객체가 Symbol.iterator() 메서드를 가지고 있으면 이터러블 프로토콜을 따름

목적: 객체가 반복 가능하게 하기 위해서(즉, 순회대상이 되기 위해서)

반환값: Symbol.iterator()를 호출하면 이터레이터 객체를 반환해야 함

사용처: for...of, 스프레드 문법(...), 배열 구조 분해 등에서 사용가능

예시객체: Array, String, Set, Map, 사용자 정의 이터러블 등

const iterable = {
  [Symbol.iterator]() {
    return {
      next() {
        return { value: 1, done: true };
      },
    };
  },
};

ex)

const isIterable = v => !== null && typeof v[Symbol.iterator] === 'function';

// 배열, 문자열, map, set 등은 이터러블이다.
isIterable([]);					// true
isIterable('');					// true
isIterable(new Map());	// true
isIterable(new Set());	// true
isIterable({});					// false

// 예를 들어, 배열은 Array.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다. 이터러블은 for...of 문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.

const array = [1, 2, 3]

// 배열은 Array.prototye의 Symbol.iterator 메서드를 상속받은 이터러블이다.
console.log(Symbol.itearble in array); // true

// ✅ 이터러블인 배열은 for...of 문으로 순회 가능하다.
for(const item of array) {
  console.log(item);
}

// ✅ 이터러블인 배열은 스프레드 문법의 대상으로 사용할 수 있다.
console.log([...array]); // [1, 2, 3]


1.2 이터레이터 프로토콜(Iterator protocol)

정의: 객체가 next() 메서드를 가지고 있고, 호출시 {value, done} 객체를 반환하면 이터레이터 프로토콜을 따름

목적: 이터러블에서 실제 값을 하나씩 꺼내는 역할 (반복의 포인터)

반환값: next()는 항상 {value: any, done: boolean} 형식의 객체를 반환해야 함

사용처: for...of등에서 내부적으로 자동 호출됨

예시객체: iterable[Symbol.iterator()]가 가변한 객체가 대부분 해당됨

const iterable = {
  next() {
    return { value: 1, done: false };
  },
};
비교항목 이터러블 프로토콜 이터레이터 프로토콜
기준 메서드 Symbol.iterator() next()
역할 “반복 가능한 객체” “반복의 진행 상태”
반환하는 것 이터레이터 객체 {value, done} 객체
누가 호출? for...of같은 반복 구문이 호출 반복 구문 내부에서 자동 호출
사용자 정의 시 반복 대상 객체의 Symbol.iterator() 구현 반환되는 이터레이터 객체의 next()구현

ex)

// 배열은 이터러블 프로토콜을 준수한 이터러블이다.
const array = [1, 2, 3];

// Symbol.iterable 메서드는 이터레이터를 반환한다.
const iterable = array[Symbol.iterator]();

// Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖는다.
console.log("next" in iterable); // true

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



2. for...of vs for...in 무슨 차이가 있는가

for...in은 단지 키(key)를 가져오는 문법

for...of는 자바스크립트의 반복 구문 중 유일하게 Symbol.iterator()를 직접호출해서 이터레이터를 사용하는 문법

const arr = ["a", "b", "c"];

for (const key in arr) {
  console.log("for...in:", key); // '0', '1', '2' (문자열 인덱스)
}

for (const value of arr) {
  console.log(`for...of`, value); // 'a', 'b', 'c' (실제 값)
}



3. 유사배열객체 vs 이터러블 vs arguments, NodeList, HTMLCollection

Photo of iterable compare

iterable_compare table

Jay Tak

3.1 유사배열객체(Array-like Object)

조건:

  • length 속성 있음
  • 숫자 인덱스로 접근 가능(예: obj[0])
  • 하지만 Symbol.iterator()가 없으면 for...of불가
const arrayLike = {0: 'a', 1: 'b', length: 2},
console.log(arrayLike[0]); // 'a'
  • 유사배열객체는 Symbol.iterator()가 있을수도 있고, 없을수도 있습니다.
// 1. Symbol.iterator가 없는 유사배열객체 (이터러블x)
const notIterable = {
  0: 'a',
  1: 'b',
  length: 2
};

console.log(notIterable[0]);       // 'a' ✅
console.log(notIterable.length);   // 2 ✅

for (const item of notIterable) {
  // ❌ TypeError: notIterable is not iterable
}

---------------------------------------------------------------------------------------------

// 2. Symbol.iterator가 있는 유사배열객체 (이터러블o)
const iterableArrayLike = {
  0: 'x',
  1: 'y',
  length: 2,
  [Symbol.iterator]() {
    let i = 0;
    return {
      next: () => {
        return i < this.length
          ? { value: this[i++], done: false }
          : { done: true };
      }
    };
  }
};

console.log([...iterableArrayLike]);  // ✅ ['x', 'y']


3.2 이터러블 객체(Iterable Object)

조건:

  • Symbol.iterator()메서드가 있음
  • for...of, 전개 연산자(...), 구조 분해 할당 등에서 사용 가능
const iterable = {
  [Symbol.iterabor]() {
    let i = 0;
    return {
      next() {
        return i < 3 ? { value: i++, done: false } : { done: true };
      },
    };
  },
};


3.3 arguments

function example() {
  for (const arg of arguments) {
    console.log(arg); // 가능 ✅ ES6 이후 가능
  }
}


3.4 NodeList

const nodes = document.querySelectorAll("div");
for (const node of nodes) {
  console.log(node); // ✅ 가능
}


3.5 HTMLCollection

const elements = document.getElementsByTagName("p");
for (const el of elements) {
  console.log(el); // ✅ 가능
}



4. 이터레이터 vs 이터러블 vs 이터러블이자 이터레이터를 생성

4.1 이터레이터

  • 이터레이터만 생성하는 함수
  • next()메서드만 있고, Symbol.Iterator()는 없음 → for...of불가
function makeIterator() {
  let i = 0;
  return {
    next() {
      return i < 3 ? { value: i++, done: false } : { done: true };
    },
  };
}

const iter = makeIterator();

console.log(iter.next()); // { value: 0, done: false }
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { done: true }

// ❌ for...of 사용 불가
// for (const val of iter) { console.log(val); } → TypeError

🧐 Q. 어떤 경우에 적합하게 사용할까?

내부적으로 직접 next()를 호출해 반복 제어하고 싶을때

// 1.1 수동으로 반복처리기 만들때
function makeIterator(max) {
  let i = 0;
  return {
    next() {
      return i < max ? { value: i++, done: false } : { done: true };
    },
  };
}

// 1.2 수동으로 반복하기
const iter = makeIterator(5);
let result = iter.next();

while (!result.done) {
  console.log(result.value); // 출력: 0, 1, 2, 3, 4
  result = iter.next(); // 수동으로 다음 값 요청
}


4.2 이터러블

  • 이터러블만 생성하는 함수
  • Symbol.iterator()는 있지만 next()는 반환된 객체만 있음 → 반복할 수 있지만 직접 next는 호출못함
function makeIterable(max) {
  return {
    [Symbol.iterator]() {
      let i = 0;
      return {
        next() {
          return i < max ? { value: i++, done: false } : { done: true };
        },
      };
    },
  };
}

const iterable = makeIterable(3);

for (const val of iterable) {
  console.log(val); // 0, 1, 2
}

// ❌ iterable.next()는 없음 → 이터레이터는 별도로 꺼내야 함
// console.log(iterable.next()); → Error

🧐 Q. 어떤 경우에 적합하게 사용할까?

for...of, ..., Array.from()등 순회 기반 문법을 자유롭게 사용하고 싶을때

// 2.1 페이징 API 시뮬레이터
function fakeApi(page) {
  const data = {
    1: ['A': 'B'],
    2: ['C': 'D'],
    3: ['E'],
    4: [] // 마지막 페이지
  };
	return data[page] || [];
}

// 2.2 이터러블 페이지네이터
function createPaginator(maxPages) {
  return {
    [Symbol.iterator]() {
     let currentPage = 1;
      return {
        next() {
          if (currentPage > maxPages) return {done: true};
          const data = fakeApi(currentPage++);
          return data.length ? {value: data, done: false} : {done: true};
        }
      };
    }
  };
}

for (const pageData of createPaginator(4)) {
  console.log('Page:', pageDate);
}

// 출력:
// Page: ['A', 'B']
// Page: ['C', 'D']
// Page: ['E']


4.3 이터러블이자 이터레이터인 객체를 생성하는 함수

  • 이터러블이자 이터레이터인 객체를 생성하는 함수
  • Symbol.iterator()this를 반환 → for...ofnext()둘 다 가능
  • 하지만 한 번만 순회 가능(소진됨)
function makeIterableIterator(max) {
  let i = 0;
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      return i < max ? { value: i++, done: false } : { done: true };
    },
  };
}

const iterBoth = makeIterableIterator(3);

for (const val of iterBoth) {
  console.log(val); // 0, 1, 2
}

for (const val of iterBoth) {
  console.log(val); // ❌ 출력 없음: 이터레이터는 소진됨
}

// ✅ 직접 next()도 호출 가능
const iter3 = makeIterableIterator(2);
console.log(iter3.next()); // { value: 0, done: false }
console.log(iter3.next()); // { value: 1, done: false }
console.log(iter3.next()); // { done: true }

🧐 Q. 어떤 경우에 적합하게 사용할까?

간단한 반복 작업을 만들고 for...of, next()를 모두 지원하고 싶을 때

// 3.1 JSON 데이터 처럼 한 번만 읽고 순회할 수 있는 스트림을 이터러블로 구현한 예제
function createOneTimeStream(dataArray) {
  let index = 0;
  return {
    [Symbol.iterator]() {
      return this; // 자기 자신이 이터레이터이자 이터러블
    },
    next() {
      return index < dataArray.length
        ? { value: dataArray[index++], done: false }
        : { done: true };
    },
  };
}

const stream = createOneTimeStream(["🧊", "🔥", "💧"]);

for (const item of stream) {
  console.log("🚚 received:", item);
}
// 🚚 received: 🧊
// 🚚 received: 🔥
// 🚚 received: 💧

for (const item of stream) {
  console.log("❌ should not appear:", item); // 출력 없음 – 이미 소진됨
}




reference: 모던자바스크립트 Deep Dive 34장. Iterable