티스토리 뷰

목적: 이터러블을 이해하고 이터레이션, 이터러블, 이터레이터, 이터러블이면서 이터레이션인 것을 구분하여 이해한다.

 

이터레이션 프로토콜은 어떤 문법이나 빌트인객체 같은것이 아니라 일종의 프로토콜입니다.

이터레이션 프로토콜에는 이터러블 프로토콜, 이터레이션 프로토콜이 있습니다.

 

이터러블

이터러블이란 이터러블 프로토콜을 준수한 객체입니다.

이터러블 프로토콜을 준수했다는 것은 객체가 Symbol.iterator 메서드를 구현하여 직접 가지고 있거나 프로토타입으로 상속받았다는 것을 말합니다. 그리고 Symbol.iterator메서드가 반환하는 객체가 이터레이터 입니다.

const arr = [1,2,3]; 은 프로토타입으로 Symbol.iterator 메서드를 상속받았네요.

두 개만 더 확인해 볼까요? 배열은 누가 봐도 순회가능하니까.. 문자열과 queryselector로 가져온 NodeList를 볼까요?

문자열도 Symbol.iterator메서드를 상속받습니다.

NodeList도 프로토타입으로 Symbol.iterator를 상속받았습니다.

반면 리터럴로 생성한 객체는 Symbol.iterator 메서드가 없네요.

즉, 배열, 문자열, NodeList는 이터러블이고 객체는 이터러블이 아닙니다.

 

이터레이터

이터레이터 프로토콜이란 next메서드를 가지며 next메서드를 호출하면 vale, done프로퍼티를 가지는 이터레이터 리절트 객체를 반환하는 것입니다. 이것을 준수한 객체가 이터레이터가 됩니다. 

이터러블이 가지고 있는 Symbol.iterator메서드를 호출하면 이터레이터를 반환합니다. 그리고 이 반환된 이터레이터는 next메서드를 가지고 있고, next메서드를 호출하면 value, done프로퍼티를 가지는 이터레이터 리절트 객체를 반환합니다.

진짜 그런지 하나씩 테스트를 해보겠습니다.

위에서 봤던 NodeList의 Symbol.iterator메서드를 호출하면 next메서드를 가지는 이터레이터가 반환되는지부터 확인해 보겠습니다.

nodeList변수에 NodeList객체를 저장하고 Symbol.iterator를 호출했습니다. 그것을 콘솔에 찍어봤더니 정말 next메서드를 상속받은 이터레이터를 반환했습니다. 그럼 next메서드를 호출해서 value, done을 가지는 객체를 반환하는지까지 확인해 보겠습니다.

정말 value, done프로퍼티를 가지는 객체, 즉 이터레이터 리절트 객체를 반환했습니다.

 

이터러블 프로토콜은 왜 필요한가

왜 이름도 낯선 Symbol.iterator메서드를 구현해 놓았을까요?

이터러블 프로토콜이 존재하기 전에는 각 자료구조들이 각자 나름의 방식으로 순회하는 메서드를 제공했습니다.

위에서 계속 나왔던 NodeList도 이터러블 프로토콜이 존재하기 전에 제공했던 순회 메서드가 있습니다.

프로토타입 메서드 말고 정적메서드 중에 forEach가 있습니다.

저 forEach 메서드로도 충분히 순회할 수 있습니다. 다만 자료구조별로 각자의 순회 메서드를 제공하다 보니 각각의 자료구조가 따로 메서드를 제공해야 하고 데이터 소비자도 데이터를 쓸 때마다 각각의 메서드를 찾아서 사용해야 되니 불편했을 것입니다.

그래서 이터러블 프로토콜을 준수한 데이터 자료는 모두 같은방식으로 순회할 수 있도록, 효율적으로 데이터를 사용할 수 있는 방식을 지원하게 된 것입니다. 

즉, 이터러블 프로토콜을 준수한 이터러블은 모두 같은 방식으로 순회할 수 있습니다.

모두 for.. of 문으로 순회할 수 있고 스프레드 연산자, 디스트럭처링 할당을 사용할 수 있습니다.

더 이상 각 자료구조별로 지원하는 순회방식을 고려하지 않아도 됩니다.

 

모든 자료구조는 이터러블 프로토콜을 준수하면 이터러블이 됩니다.

다시 말해 내부에 Symbol.iterator메서드를 구현하고 그것을 호출했을 때 next메서드를 가지는 이터레이터를 반환하며 next메서드를 호출했을때 value와 done을 가지는 이터레이터 리절트 객체를 반환하면 이터러블이 됩니다.

 

const simpleObj = {
    [Symbol.iterator](){
        return {
            next(){
                return {value: , done:}
            }
        }
    }
}

위에서 객체는 이터러블이 아니라고 했습니다만, simpleObj 객체는 Symbol.iterator메서드를 가지고 있으며,

next메서드를 가진 이터레이터를 반환하고, 이터레이터는 value, done프로퍼티를 가진 이터레이터 객체를 반환하므로

simpleObj는 이터러블이 됩니다. 그래서 for.. of로 순회할 수 있게 됩니다. 

simpleObj를 피보나치수열을 반환하는 객체로 테스트해볼까요?

const simpleObj = {
  [Symbol.iterator]() {
    let [pre, cur] = [0, 1];
    const max = 10;
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { value: cur, done: cur >= max };
      },
    };
  },
};

for (let value of simpleObj) {
  console.log(value); // 1,2,3,5,8
}

객체를 for.. of문으로 순회했습니다.

 

위 for of문이 실행되는 모습을 일반 for문으로 만들어보겠습니다.

const iterator = simpleObj[Symbol.iterator]();
for (;;) {
  const { value, done } = iterator.next();
  if (done) break;
  console.log(value); // 1,2,3,5,8
}

Symbol.iterator를 호출해서 이터레이터를 반환받고 next를 한 번씩 호출하면서 done이 true가 되면 for문을 이탈하고 아니면 value를 출력하도록 했습니다. 

 

이번에는 '이터러블인 객체' 말고 '이터러블인 객체를 반환하는 함수'를 만들어보겠습니다.

const fibonacci = (max) => {
  let [pre, cur] = [0, 1];
  return {
    [Symbol.iterator]() {
      return {
        next() {
          [pre, cur] = [cur, pre + cur];
          return { value: cur, done: cur >= max };
        },
      };
    },
  };
};

const iterator = fibonacci(10)[Symbol.iterator]();

for (;;) {
  const { value, done } = iterator.next();
  if (done) break;
  console.log(value); // 1,2,3,5,8
}

fibonacci 함수가 이터러블인 객체를 반환하기 때문에 fibonacci 함수를 호출하고 

Symbol.iterator메서드를 호출해서 이터레이터를 반환받아서 for문을 돌렸습니다.

 

이터러블이면서 이터레이터인 객체

위 fibonacci 함수를 조금 바꿔보겠습니다.

const fibonacci = (max) => {
  let [pre, cur] = [0, 1];
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      [pre, cur] = [cur, pre + cur];
      return { value: cur, done: cur >= max };
    },
  };
};

const iterator = fibonacci(10);

for (;;) {
  const { value, done } = iterator.next();
  if (done) break;
  console.log(value); // 1,2,3,5,8
}

fibonacci 함수가 Symbol.iterator메서드가 구현된 객체를 리턴하긴 하는데, Symbol.iterator가 next메서드를 가진 이터레이터를 반환하는 게 아니라 this를 반환합니다. 그리고 그 this는 next메서드를 가진 객체이므로 적절한 Symbol.iterator메서드가 되었습니다.

즉, fibonacci 함수가 반환하는 객체는 이터러블이면서(Symbol.iterator메서드를 가짐) 이터레이터(next메서드가 이터레이터 리절트 객체를 리턴함)입니다.

이렇게 이터러블이면서 이터레이터인 객체를 만들면

const iterator = simpleObj[Symbol.iterator]();

이터레이터를 얻기 위해 이렇게 하지 않아도 됩니다.

const iterator = fibonacci(10);

이렇게만 해도 iterater 식별자에는 이터러블이면서 이터레이터인 객체가 담기기 때문에 저 자체로 순회가 가능합니다.

 

지연평가

이터러블을 사용하면 데이터가 지연평가되어 무한을 표현할 수 있다고 합니다.

예를 들어 얼마나 큰 피보나치 수열이 필요할지 모르는 상황이라고 생각해 보겠습니다.

배열에 이런 수열을 만들어볼까요?

const infinityFibonacci = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765];
// ....?

어떻게 만들죠..? 얼마나 커질지 모른다는 건, 무한인데?

그렇습니다. 배열은 이미 데이터를 메모리에 확보한 다음에야 순회를 할  수 있습니다.

그래서 무한을 표현할 수 없습니다. 왜냐하면 무한한 메모리를 확보할 수는 없는 거니까요.

하지만 이터러블의 데이터는 지연평가 되기 때문에 무한할 수 있습니다. 

음... 무한하기보다는 결국 얼마나 크든 원하는 값이 있을 테니 그 값이 필요할 때 얼마든 도달할 수 있다고 표현해야 할까요

 

const infinityFibonacci = () => {
  let [pre, cur] = [0, 1];
  return {
    [Symbol.iterator]() {
      return this;
    },
    next() {
      [pre, cur] = [cur, pre + cur];
      return {
        value: cur,
      };
    },
  };
};

infinityFibonacci함수는 이터러블이면서 이터레이터인 객체를 리턴하는 함수입니다.

근데 이터레이터 리절트객체의 done값이 없네요. done이 true가 되는 순간 순회가 멈춰야 하는데 

done이 없어졌으니 무한하게 순회합니다. 대신 다른 점이 있어요.

for (let num of infinityFibonacci()) {
  if (num > 100) break;
  console.log(num);
}

done이 없어졌으므로 순회의 이탈을 num값으로 했습니다.

위 for of문을 일반 for문으로 표현해 보겠습니다.

const iterator = infinityFibonacci();
for (;;) {
  const { value } = iterator.next();
  if (value > 100) break;
  console.log(value);
}

위 for문에서 보면 iterator.next()가 반환하는 value가 언제 평가되는지 알 수 있습니다.

바로 next메서드가 호출되는 순간입니다. 

만약 break 조건이 value >10000이라면 반환한 value가 10000보다 작은지 판단한 후 value가 10000보다 작다면 다시 for문의 next메서드를 호출하고 새로운 value를 반환합니다. 그러므로 10000보다 작은 피보나치수열을 미리 배열에 담아두지 않아도 조건문에 도달하지 않는다면 계속해서 더 큰 피보나치 수열을 평가할 수 있습니다. 

이것을 지연평가라고 하고 순회 이탈만 제대로 할 수 있다면 무한히 큰 수열도 평가할 수 있게 됩니다.

 

지연평가를 사용하면 배열의 순회 성능을 높일 수 있다고 합니다. 

다만 제너레이터 함수를 사용하면 위에서 이터러블 프로토콜을 준수해서 만드는 것보다 훨씬 쉽게 만들 수 있어서

지연평가 기법을 사용하기 위해 제너레이터를 많이 사용하는 것 같습니다!

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/04   »
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
글 보관함