본문 바로가기

Web + APP/JavaScript

Iterator / Generator

반응형
SMALL

안녕하세요. 꼬동입니다.

 

현재 저는 다양한 준비를 하고 있는데, 그 준비과정에서 만난 JS의 Iterator / Generator에 대해서 자세히 알아보려고 합니다.

 

갑시다 !

 


이터레이터는 '지금 어디 있는지' 파악할 수 있도록 돕는 책갈피와 비슷한 개념입니다.

 

배열은 이터러블 객체의 좋은 예인데, 배열에 values 메서드를 써서 이터레이터를 만들 수 있습니다.

 

이 상태에서 next 메서드를 호출하면 value 프로퍼티와 done 프로퍼티를 반환합니다.

 

const it = [1,2,3].values();
it.next() // {value: 1, done: false}
it.next() // {value: 2, done: false}
it.next() // {value: 3, done: false}
it.next() // {value: undefined, done: true}

// iterator는 모두 독립적이다
const it1 = [1,2,3].values();
const it2 = [1,2,3].values();

it1.next() // 1
it2.next() // 1

 

js의 for loop의 경우엔 배열 요소의 인덱스는 숫자형이고 순차적으로 인덱스 변수를 써서 해다하는 배열 요소에 접근할 수 있는데, for of 루프는 이가 어떻게 가능한 것일까요?

 

바로 이터레이터를 써서 했엇던 것이었습니다. done이 false가 뜰 때까지 도는거죠.

 

Iterator / Genrator 개념이 ES6부터 시작했으니, 당연히 이와 관련된 for ... of도 IE11에선 쓸 수 없겠죠? (물론 polyfill을 만들면 됩니다)

 


Iterator 자체는 크게 쓸모 있지 않지만, 쓸모 있는 동작이 가능해집니다. 모든 객체를 Iterable 객체로 바꿀 수 있습니다.

 

멤버 변수로 배열을 가지고 있는 클래스를 생각해봅시다. 이 클래스에 객체를 만들고, 이 배열을 순회한다고 했을 때, 멤버 변수에 직접 접근해서 순회하는 것은 위험하다고 판단이 됩니다.

 

그렇다면 저희는 이터러블 객체로 바꾼다면 이 모든게 해결이 되겠죠.

 

이터레이션 프로토콜의 경우엔 클래스에 Symbol.iterator 메서드가 있고, {value: any, done: boolean}을 반환한다면, 클래스의 인스턴스는 객체가 되는 것입니다.

 


그렇다면 Generator는 무엇일까요?

 

사실 이 부분에 대해선 제가 착각했던 부분이 있습니다.

 

언어마다 Iterator / Generator가 조금씩 다르다는 부분이었는데, 이 글에서는 JS 기준으로 설명을 드리겠습니다.

 

Generator란 이터레이터를 사용해 자신의 실행을 제어하는 함수입니다. 두 가지 개념이 생겼는데,

 

1. 함수의 실행을 개별적 단계로 나눔으로써 함수의 실행을 제어한다. (제너레이터는 호출한 즉시 실행되지 않고, 이터레이터를 반환하고, 이터레이터의 next 메서드를 호출함에 따라 실행됩니다)

 

2. 실행 중인 함수와 통신한다. (제너레이터는 언제든 호출자에게 제어권을 넘길(yield) 수 있습니다)

 

이 두 가지 예외를 제외하면 일반적인 함수와 같습니다.

 

제너레이터를 만들 때는 function 키워드 뒤에 애스터리스크(*)를 붙입니다. 이를 제외하면 일반 함수와 같습니다. 또한, return 외에 yield 키워드를 쓸 수도 있죠.

 

function *rainbow() {
    yield 'red';
    yield 'orange';
    yield 'yellow';
}

const it = rainbow();
it.next(); // red / false
it.next(); // orange / false

// for ... of에도 사용이 가능합니다.
for (let color of rainbow()) {
	console.log(color);
}

 

앞서 제너레이터와 호출자 사이에어 양방향 통신이 가능하다고 말했는데, 통신은 yield 표현식을 통해 이뤄집니다.

 

yield 표현식의 값은 호출자가 제너레이터의 이터레이터에서 next를 호출할 때 제공하는 매개변수입니다.

 

제너레이터는 화살표 표기법으로 만들 수 없으며 반드시 function*을 써야 합니다.

 

또한, 제너레이터에 return의 경우엔 그 위치와 상관없이 done은 true가 되고 value 프로퍼티는 return이 반환하는 값이 됩니다.

 

이런 동작 방식이 정확하기는 하지만, 제너레이터를 사용할 때는 보통 done이 true이면 value 프로퍼티에 주의를 기울이지 않는다는 것을 기억하시면 됩니다.

 

function* abc() {
	yield 'a';
    yield 'b';
    return 'c';
}

const it = abc();
it.next();	// {'a', false}
it.next();  // {'b', false}
it.next();  // {'c', true}

// 'c'는 출력되지 않습니다.
for (let l of abc()) {
	console.log(l);
}
// a
// b

Iteraotr는 배열이나 객체처럼 여러 가지 값을 제공할 수 잇는 컬렉션이 동작 방식을 표준화했습니다. ES6에서 추가가 됐지만, 이를 이전에 사용하지 못했다는 것이 아니고 표준화 했다는 점을 강조드립니다.

 

Generator를 사용하면 함수를 훨씬 더 효율적으로 사용할 수 있습니다. 호출한 함수가 완료되길 기다렸다가 반환값을 받는다는 사고방식에 얽매일 필요가 없죠.

 

이상 Iterator / Generator였습니다. ^_^

반응형
LIST