우선 iterator의 사전적 뜻은 반복자
이다. 그러다면 우선 반복문에 대하여 알아보자.
ES5 : array.forEach 사용. break가 불가하다.
ES6 : for
(const item of
array) 사용. 배열에서만 사용 가능하다(for - of 구문).
ES6 : for
(const item in
object) 사용. 배열 외(객체 등)를 순회할 경우에 사용한다(for - in 구문).
이때 ES6의 for in 구문은 index를 증가하면서 도는 것이 아닌 객체를 순회하며 반복문을 도는 것이다. 여기서 iterator가 등장한다.
property이고, iterator 함수가 구현되어 있으면 iterable하다고 부른다.
즉, iterator는 객체를 순회하는 연산자이고, iterator 함수가 구현되어 있는 것이 iterable이다.
array도 iterable 하다고 볼 수 있다.
for of
loop 를 사용하여 iterator item을 가져올 수 있다.
배열에서 distructuring를 통하여 아이템을 추출하는 것도 가능하다.
우선 python이나 js 같은 스크립트 언어를 사용하다보면 언어자체에서 배열과 iterator를 섞어서 쓸 수 있게 제공을 하고있다.
쉽게 말해 우리가 iterator다~ 라고 하는 것은 iterable protocol과 iterator protocol을 두가지 모두 구현한 것이다. 그럼 우선 코드를 봐보자
const array = [1,2,3,4,5,6,7]
const iterator = (function(){
let num = 1;
return {
next : function () {
return (
num > 7 ?
{done : true} :
{done : false value : num++}
)
}
}
})
iterator는 기본적으로 next 함수를 반환하는데 이 next 함수는 boolean
값의 done
과 any
타입의 value
를 갖고있다. 즉, 이전에 얻어온 값 바로 다음 값만 가져올 수 있다.
이때 iterator는 done은 끝이 났는지를 의미하고 value는 iterator가 아직 돌고있을 때 값을 반환한다.
반면 array의 경우 random access가 가능하다. 즉, 인덱스를 갖고있는 어느 값이든 아무때가 가져오는게 가능하고 또한 .length도 바로 알 수 있다. 즉, array가 기능이 더 많기 때문에 아래 사진과 같다고 볼 수 있다.
즉, 배열은 언제든 iterator로 변환할 수 있다. 반면, iterator는 array가 되려면 계산하지 않고 두던 모든 값을 계산해야한다. 즉, iterator의 next를 모두 불러서 iterator 순회를 다 끝내야 가능하다. 그것을 모아야 배열로 return이 가능하다.
그럼 array가 훨씬 편한데 왜 굳이? iterator가 존재할까?
array가 기능이 많은 만큼 당연히 더 무거울 수 밖에 없다.
즉, 코드 가독성과 코드 퍼포먼스가 떨어질 수 밖에 없다.
array는 사용을 하려면 당연 array내의 모든 데이터를 메모리에 올려야한다.
하지만 ierator같은 경우는 하나의 변수만 메모리에 올리면 되기 때문에 메모리를 방대하게 잡아먹는 이미지 같은 데이터를 처리할 때 array에 비해 매우 유리한 위치에 있다.
일정 규칙이 존재하는 수열과 같은 데이터를 다루기 위해서는 iterator를 사용하는것이 더 좋다.
class CustomIterable implements Iterable<string> {
private _arr: Array<string> = ['one', 'two']
[Symbol.iterator]() {
let nextIndex = 0
return {
next: () => {
return {
value: this._arr[nextIndex++]
done: nextIndex < this._arr.length
}
}
}
}
}
const customIterable = new CustomIterable()
for (const item of customIterable._arr) {
console.log(item) // 'one', 'two'
}
function getIterator(){
let num = 1;
const next = () => (
num > 7 ?
{done : true} :
{done : false, value : num++ }
);
return {
[Symbol.iterator]: () => ({next}),
next,
};
}
function* getGenerator(){
for (let num = 1; num <= 7; num++){
yield num;
}
}
for (const num of getIterator()){
console.log(num);
} // 1 ~ 7 까지 print
[...getIterator()] // [1,2,3,4,5,6,7]
[...getGenerator()] // [1,2,3,4,5,6,7]
function*
과 return 대신 yield
문법을 상용하면 된다. 엄밀히 말하면 generator가 iterator보다 좀 더 많은 기능을 제공한다.
iterator같은 경우는 for loop이나 distructuring 할 때 추가적인 작업이 필요하다.
그것을 iterable protocol이라고 부른다. => Symbol.iterator
반면 generator는 기본적으로 iterator protocol을 지원한다. 그렇다보니 실무에서는 iterator 보단 generator를 사용하는 편이 많기도 하다.
iterator와 iterable의 조합이라고 말할 수 있다.
우선 비슷한 예제를 봐보자.
갑자기 웬 promise?! 할 수 있지만 이것은 기존 JS 함수 선언 규칙을 확장했다는 점에서 공통점을 갖는다.
기존 JS는 return에 특정 값을 넣으면 함수는 그 해당 값을 반환한다.
하지만 async/await는 조금 다르다. 함수 앞에 prefix로 async를 붙이면 해당함수는 더이상 return한 값을 그대로 반환하지 않는다. 대신 Promise 인스턴스를 반환한다.
async
는 키워드가 반환하는 타입이 달라졌다는 것을 암시하고 있는 것이다.
마찬가지로 function 예약에 뒤에 *
가 붙으면 이것은 return type이 iterator라는 것을 알려주는 것이다.
즉, Promise와 async/await이 하는 일은 같지만 Promise를 보다 쉽게 사용하려고 async/await을 사용하는 것처럼
generator도 iterator를 보다 쉽게 사용하기위하여 사용하는 것과 같다.