티스토리 뷰

Javascript

iterator 와 generator

mongoT 2020. 3. 1. 18:34

ES6에서 생겨난 개념.

 

iterator와 generator.

 

generator는 iterator에 의존하는 개념이기 때문에 iterator부터 시작하자.

iterator의 'iterate'는 '순회하다.'라는 뜻이다.

뜻에서 짐작할 수 있듯이 배열이나 객체의 값을 순회하는 역할이다.

어떻게 순회하는지 확인해보자.

 

iterable의 대표적인 예인 배열부터 보면

const book = [
    "hi",
    "i'm",
    "a developer"
];

iterator는 values() 메서드를 써서 이터레이터를 만들 수 있다.

const it = book.values();

next() 메서드를 써서 순회를 진행할 수 있다.

console.log(it.next()); // { value: 'hi', done: false }
console.log(it.next()); // { value: "i'm", done: false }
console.log(it.next()); // { value: 'a developer', done: false }
console.log(it.next()); // { value: undefined, done: true }

done이 true가 되면 순회가 끝난 것이다.

for... of 구문에서 인덱스가 없이 순회할 수 있는 이유도 바로 이 iterator 때문이다.

for(let n of book){
    console.log(n);
}
// hi
// i'm
// a developer

for(let n of book.values()){
    console.log(n);
}
// hi
// i'm
// a developer

iterator는 독립적이다.

const it = book.values();
const it2 = book.values();

console.log(it.next()); // { value: 'hi', done: false }
console.log(it2.next()); // { value: 'hi', done: false }

이터레이터 프로토콜(iterator protocol)은 모든 객체를 iterable하게 바꿀 수 있다.

이터레이터 프로토콜을 사용하는 법은 클래스에 심벌 메서드 Symbol.iterator가 있고,

이 메서드가 이터레이터처럼 동작하는 객체, 즉 value와 done프로퍼티가 있는 객체를 반환한다면

그 클래스의 인스턴스는 iterable 하다고 할 수 있다. 

class Log {
  constructor() {
    this.messages = [];
  }
  add(message) {
    this.messages.push({ message, timestamp: Date.now() });
  }
  [Symbol.iterator](){
      return this.messages.values();
  }
}

이 클래스는 Symbol.iterator를 가지고 있고 이 메서드는 iterator를 반환하므로 iterable 하다.

const log = new Log();
log.add("hi");
log.add("i'm");
log.add("a developer");

for(let v of log){
    console.log(v);
}
// { message: 'hi', timestamp: 1583050171741 }      
// { message: "i'm", timestamp: 1583050171741 }     
// { message: 'a developer', timestamp: 1583050171741 }

iterator를 직접 구현할 수도 있다.

class Log {
  constructor() {
    this.messages = [];
  }
  add(message) {
    this.messages.push({ message, timestamp: Date.now() });
  }
  [Symbol.iterator]() {
    let i = 0;
    const messages = this.messages;
    return {
      next() {
        if (i < messages.length) {
          return { value: messages[i++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

iterator의 순회 그리고 done을 true로만 만들면 무한한 특성을 이용해 간단하게 피보나치수열을 구해보면

class FibonacciSequence {
  [Symbol.iterator]() {
    let a = 0,
      b = 1;
    return {
      next() {
        let value = b;
        b +=a;
        a = value;
        return {value, done:false};
      }
    };
  }
}
const fib = new FibonacciSequence();

let i = 0;
for (let v of fib) {
  if (i++ > 9) break;
  console.log(v);
}

generator

generator는 iterator를 사용해 자신의 실행을 제어하는 함수다.

일반적인 함수의 경우에는 호출을 한 경우 호출자는 함수의 실행에 대해서 제어권이 없어서 함수가 종료될 때까지 기다리는 수밖에 없다.

generator는 다음과 같은 두 가지 새로운 개념을 도입했다.

 

1. 함수의 실행을 개별적 단계로 나눔으로써 함수의 실행을 제어한다는 것.

2. 함수와 통신이 가능하다는 것.

 

제너레이터를 만들 때는 뒤에 *를 붙인다.

제너레이터에서는 return키워드 외에도 yield를 쓸 수 있다.

function* rainbow(){
    yield 'red';
    yield 'orange';
    yield 'yellow';
    yield 'green';
    yield 'blue';
    yield 'indigo';
    yield 'violet';
}

yield 키워드를 호출자에게 제어권을 양도(yield) 할 수 있다.

const it = rainbow();

console.log(it.next()); // { value: 'red', done: false }
console.log(it.next()); // { value: 'orange', done: false }

for... of에서도 사용 가능하다.

const it = rainbow();

for(let v of it){
    console.log(v);
}

이제 이 yield에 대해서 알아보자. 

function* communicate() {
  const name = yield "What is your name?";
  const color = yield "What is your favorite color?";
  return `${name}'s favorite color is ${color}`;
}

const it = communicate();

console.log(it.next()); // { value: 'What is your name?', done: false }
console.log(it.next('Jack')); // { value: 'What is your favorite color?', done: false }
console.log(it.next('orange')); // { value: "Jack's favorite color is orange", done: true }

it.next()를 통해서 제어권을 넘겨준다(yield). 넘겨줄 때 iterator에서 쓰이는 value값을 넣어줄 수 있다.

그 다음행에 it.next('Jack')을 통해서 다시 호출하면 yield 'What is your name?' 이 전체가 'Jack'이 된다.

다시 제너레이터는 제어권을 호출자에게 넘겨주고 yield 'What is your favorie color?' 전체가 'orange'가 된다. 그리고 마지막에 return을 함으로써 제너레이터 실행이 종료된다.

 

generator에서는 마지막이 {value: undefined, done: true}가 아니다.

const it = communicate();

for(let v of it){
    console.log(v);
}
// What is your name?
// What is your favorite color?

for... of 루프에서는 return값을 출력할 수 없다.

generator에서 되도록이면 return값에 중요한 값을 입력하지 않아야 한다.

 

 

참고문헌: Learning Javascript - 이선 브라운

'Javascript' 카테고리의 다른 글

다형성과 다중 상속  (0) 2020.03.01
객체프로퍼티 나열  (0) 2020.03.01
Map과 Set  (0) 2020.03.01
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
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
글 보관함