자바스크립트 완벽 가이드 12장 정리 ( Iterator & Generator )
포스트
취소

자바스크립트 완벽 가이드 12장 정리 ( Iterator & Generator )

해당 포스트는 자바스크립트 완벽 가이드라는 교재로 스터디를 하면서 12장을 정리한 포스트입니다.
주관적으로 해석한 내용이 들어가 있어서 잘못된 내용이 포함될 수 있습니다.
또한 교재의 모든 내용을 정리하지 않고 주관적인 판단에 의해 필요한 내용만 작성했습니다.

🖐️ 이터레이터와 동작 방법

0️⃣ 이터러블 객체

Array, Set, Map, Generator, 문자열과 같은 이터레이터 프로토콜을 만족하는 객체를 의미합니다.

1️⃣ 이터레이터 메서드

순회하는데 사용하는 next() 메서드를 가지는 객체를 반환하는 메서드입니다.

2️⃣ 이터레이터 객체

next() 메서드가 반환하는 객체로 valuedoneproperty로 갖습니다.
( { done: boolean, value: any } )

3️⃣ 동작 방법

  1. 이터러블 객체를 순회 요청
  2. 이터레이터 메서드 호출 ( next() 메서드를 갖는 객체 반환 )
  3. 이터레이터 객체의 next() 호출
  4. next()의 반환 값인 객체의 donetrue일 때까지 이터레이터 메서드 호출을 반복

이터레이터 메서드를 호출하는 방법 즉, 이터레이터 메서드가 갖는 keySymbol.Iterator로 호출이 가능합니다.

✍️ 이터러블 작성 예시

0️⃣ 이터러블, 이터레이터 객체, 이터레이터 객체

이터러블이 되려면 반드시 이터레이터 메서드를 가져야 합니다.
그리고 이터레이터 메서드는 이터레이터 객체를 반환해야 합니다.
그리고 이터레이터 객체는 next()메서드를 갖고 그 메서드는 { done: boolean, value: any }형태의 값을 반환해야합니다.

아래의 예시는 위 조건을 다 지킨 이터러블한 객체입니다.
이터러블하기 때문에 spread operator를 이용해서 배열에 사용할 수 있고, for ~ of도 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 이터러블
// 1. 이터레이터 메서드를 가짐
// 2. 이터터레이터 메서드가 이터레이터 객체를 반환함
const iterable = {
  from: 1,
  to: 5,

  // 이터레이터 메서드
  // 1. "Symbol.iterator"을 "key"로 가짐
  // 2. "next()" 메서드를 갖는 객체를 반환
  [Symbol.iterator]() {
    let from = this.from;
    let to = this.to;

    return {
      // 이터레이터 객체
      // 1. "{ done: boolean, value: any }" 형태를 반환함
      next() {
        if (to < from) {
          return { done: true, value: from++ };
        }
        return { done: false, value: from++ };
      },
    };
  },
};

console.log([...iterable]); // [1, 2, 3, 4, 5]

1️⃣ 이터러블한 객체를 만드는 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 이터러블한 객체를 생성하는 클래스
class Range {
  constructor(from, to) {
    this.from = from;
    this.to = to;
  }

  has(x) {
    return typeof x === "number" && this.from <= x && x <= this.to;
  }

  toString() {
    return `{ x | ${this.from} <= x <= ${this.to} }`;
  }

  // 이터레이터 메서드 정의 ( "Symbol.iterator"로 정의 )
  [Symbol.iterator]() {
    let next = Math.ceil(this.from);
    let last = this.to;

    // 이터레이터 객체 반환
    return {
      next() {
        return next <= last
          ? { value: next++, done: false }
          : { value: next, done: true };
      },
      // 이걸 왜 하는 거죠..?
      [Symbol.iterator]() {
        return this;
      },
    };
  }
}

const range = new Range(1, 5);

console.log([...range]); // [1, 2, 3, 4, 5]

🦾 제너레이터 함수

제너레이터 함수는 함수 바디를 부분적으로 실행하고 그 결과를 반환하는 함수입니다.
그리고 부분적으로 실행한 결과는 즉, 반환 값은 이터레이터 객체입니다.

0️⃣ 동작 방식

최초에 제너레이터 함수를 실행하면 함수 바디가 실행되는 것이 아닌 이터레이터 객체를 반환합니다.
그리고 이터레이터 객체의 next() 메서드를 호출하면 제너레이터 함수 바디가 실행되고 yield를 만나면 멈추고 이터레이터 객체를 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function* onDigitPrimes() {
  yield 2; // (1)
  yield 3; // (2)
  yield 5; // (3)
  yield 7; // (4)

  return "the end"; // (5)
}

const primes1 = onDigitPrimes();
console.log(primes1.next()); // (1) { value: 2, done: false }
console.log(primes1.next()); // (2) { value: 3, done: false }
console.log(primes1.next()); // (3) { value: 5, done: false }
console.log(primes1.next()); // (4) { value: 7, done: false }
console.log(primes1.next()); // (5) { value: "the end", done: true }
console.log(primes1.next()); // (6) { value: undefined, done: true }
console.log(primes1.next()); // (7) { value: undefined, done: true }

const primes2 = onDigitPrimes();
console.log(primes2[Symbol.iterator]().next()); // (1) { value: 2, done: false }
console.log(primes2[Symbol.iterator]().next()); // (2) { value: 3, done: false }
console.log(primes2[Symbol.iterator]().next()); // (3) { value: 5, done: false }
console.log(primes2[Symbol.iterator]().next()); // (4) { value: 7, done: false }
console.log(primes2[Symbol.iterator]().next()); // (5) { value: "the end", done: true }
console.log(primes2[Symbol.iterator]().next()); // (6) { value: undefined, done: true }
console.log(primes2[Symbol.iterator]().next()); // (7) { value: undefined, done: true }

1️⃣ 제너레이터 함수 값 주고 받기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function* smallNumbers(v) {
  console.log("첫 인자 >> ", v);
  let y1 = yield 1;
  console.log("y1 >> ", y1);
  let y2 = yield 2;
  console.log("y2 >> ", y2);
  let y3 = yield 3;
  console.log("y3 >> ", y3);
  let y4 = yield 4;
  console.log("y4 >> ", y4);

  return 5;
}

let g = smallNumbers(0);

// 첫 인자 >>  0 // ( 첫 실행의 인자인 "11"은 무시됨 )
console.log(g.next("11")); // { value: 1, done: false }
// y1 >>  22
console.log(g.next("22")); // { value: 2, done: false }
// y2 >>  33
console.log(g.next("33")); // { value: 3, done: false }
// y3 >>  44
console.log(g.next("44")); // { value: 4, done: false }
// y4 >>  55
console.log(g.next("55")); // { value: 5, done: false }

console.log(g.next("66")); // { value: undefined, done: false }
console.log(g.next("77")); // { value: undefined, done: false }

/**
 * 첫 인자 >>  0
 * { value: 1, done: false }
 * y1 >>  22
 * { value: 2, done: false }
 * y2 >>  33
 * { value: 3, done: false }
 * y3 >>  44
 * { value: 4, done: false }
 * y4 >>  55
 * { value: 5, done: true }
 * { value: undefined, done: true }
 * { value: undefined, done: true }
 */

2️⃣ 제너레이터와 이터러블

제너레이터 함수가 동작하는 구조를 살펴보면 이터레이터 메서드를 반환하고 그 메서드가 이터레이터 객체를 반환하는 형식이 이터러블 객체와 같습니다.
따라서 제너레이터는 기본적으로 이터러블합니다.

1
2
3
4
5
6
7
8
9
10
11
function* onDigitPrimes() {
  yield 2;
  yield 3;
  yield 5;
  yield 7;

  return "the end";
}

const primes3 = onDigitPrimes();
console.log(...primes3); // 2 3 5 7

제너레이터를 이용해서 보다 쉽게 이터러블을 구현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 이터러블
// 1. 이터레이터 메서드를 가짐
// 2. 이터터레이터 메서드가 이터레이터 객체를 반환함
const iterable = {
  from: 1,
  to: 5,

  // 이터레이터 메서드
  // 1. "Symbol.iterator"을 "key"로 가짐
  // 2. "next()" 메서드를 반환
  *[Symbol.iterator]() {
    let from = this.from;
    let to = this.to;

    for (let index = from; index <= to; index++) {
      yield index;
    }
  },
};

console.log([...iterable]); // [1, 2, 3, 4, 5]

3️⃣ yield vs yield*

yield는 하나의 값을 전달하지만, yield*는 전달된 이터러블 객체를 모두 순회하고 그 값을 전달합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// yield
function* sequence(...iterables) {
  for (const iterable of iterables) {
    for (const item of iterable) {
      yield item; // 하나의 값만 반환 ( a, b, 1, 2, d, e )
    }
  }
}

console.log(...sequence("ab", [1, 2], "de")); // a b 1 2 d e

// =============================================================== //

// yield*
function* sequence(...iterables) {
  for (const iterable of iterables) {
    yield* iterable; // 전달된 이터러블("iterable")을 모두 실행해서 반환
  }
}

console.log(...sequence("ab", [1, 2], "de")); // a b 1 2 d e

📮 레퍼런스

  1. « 자바스크립트 완벽 가이드 12장 » ( 데이비드 플래너건 지음, 한성용 옮김, 인사이트, 2022 )
  2. 1-blue - 이터레이터 프로토콜
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

자바스크립트 완벽 가이드 10장 정리 ( module )

타입스크립트 함수 오버로딩 ( Overloading )