대부분 리스트를 순회하는 코드를 작성해 보라고 하면 다음과 같이 작성한다.
const list = [1, 2, 3];
for (let i = 0; i < list.length; i++) {
console.log(list[i])
}
// 1
// 2
// 3
// undefined
하지만 ES6이후 새롭게 추가된 문법으로 위와 같은 코드를 조금 더 간결하고 직관적으로 표현할 수 있다.
const list = [1, 2, 3];
for (const a of list) console.log(a)
// 1
// 2
// 3
// undefined
그렇다면 Set 자료구조도 위와 같은 코드로 순회가 가능할까?
const set = new Set([1, 2, 3]);
for (const a of set) console.log(a)
// 1
// 2
// 3
// undefined
가능하다.
그렇다면 Map은?
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map) console.log(a)
// ['a', 1]
// ['b', 2]
// ['c', 3]
// undefined
역시 가능하다.
왜 이게 가능할까? 이를 이해하려면 이터러블과 이터레이터에 대한 이해가 필요하다.
이터러블, 이터레이터
이터러블 : 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진 값
이터레이터: { value, done } 객체를 리턴하는 next() 를 가진 값
iterator라는 변수를 선언하고 list안에 있는 [Symbol.iterator]()을 할당해 보자.
그리고 iterator안에 next()의 값을 찍어보면 다음과 같다.
Set과 Map도 똑같이 console에 찍어보면,
이런 식으로 반환된다.
이터레이터는 { value, done } 을 반환한다는 것을 확인했다.
위 코드에서 이터레이터가 value와 done을 반환한다는 내부적인 로직을 이해했다면 map함수를 이렇게 활용할 수 있다.
그렇다면 이터러블을 생성한다면 더욱더 확장성 있는 코드를 작성할 수 있지 않겠는가?
이를 가능하게 해주는 것이 제너레이터이다.
제너레이터
제너레이터 : 이터레이터이자 이터러블을 생성하는 함수
제너레이터는 함수명 앞에 *을 붙여서 선언하면 된다.
간단한 제너레이터 함수는 다음과 같다.
이를 응용해서 홀수만 찾는 로직을 만들어 보면,
function *infinity(i = 0) {
while (true) yield i++;
}
function *limit(l, iter) {
for (const a of iter) {
yield a;
if (a === l) return;
}
}
function *odds(l) {
for (const a of limit(l, infinity(1))) {
if (a % 2) yield a;
}
}
for (const a of odds(10)) console.log(a);
// 1
// 3
// 5
// 7
// 9
// undefined
이 처럼 구현할 수 있다.
놀라운 건
function *infinity(i = 0) {
while (true) yield i++;
}
는 무한루프에 걸리지 않고 suspended 된다는 점이다.
참고한 사이트
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
마치며
자바스크립트의 iterator, generator에 대해 알아봤다.
JavaScript로 더 나은 코드, 버그 없는 코드, 확장성 높은 코드를 만들기 위해 더 좋은 개발자가 되기 위해선 꾸준히 공부하고 새로운 것을 받아들여야 한다. 특히 프런트엔드 개발자는 새로운 기술에 대해 더욱 유연해야 한다고 생각한다.
요즘 나의 코드들을 살펴보면 관성으로 작성했던 코드, 잘 돌아가는 코드를 작성하게 되는데 이러한 관성을 버리려고 노력하고 새로운 것들을 받아들여 많이 틀려보고 다시 고쳐보는 경험을 해야겠다.
끝.