ES6에서 도입된 이터레이션 프로토콜(iteration protocol) 또는 이터러블(반복 가능한, iterable) 객체는 데이터 컬렉션을 순회하기 위한 프로토콜(미리 약속된 규칙)이다. 이터레이션 프로토콜을 준수한 객체는 for…of 문으로 순회할 수 있고 Spread 문법의 피연산자가 될 수 있다.
이터러블 : 이터레이터를 리턴하는 Symbol.iterator를 가진 값.
Symbol.iterator 메소드를 구현하거나 프로토타입 체인에 의해 상속한 객체를 말한다.
이터레이터 : { value, done } 객체를 리턴하는 next()를 가진 값.
next 메소드를 소유하며 next 메소드를 호출하면 이터러블을 순회하며 value, done 프로퍼티를 갖는 이터레이터 객체를 반환한다.
const arr = [1, 2, 3];
let iterator = arr[Symbol.iterator](); // [Function: entries]
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
for (const x of arr) console.log(a) // 1 2 3
// for...of 문으로 순회 시 value 값을 순서대로 순회하며, done 값이 true가 될 때 for 문을 빠져나온다.
// Array, Set, Map 모두 사용이 가능하다.
const set = new Set([1, 2, 3]);
let iterator2 = set[Symbol.iterator](); // [Function: entries]
console.log(iterator2.next()); // { value: 1, done: false }
console.log(iterator2.next()); // { value: 2, done: false }
console.log(iterator2.next()); // { value: 3, done: false }
console.log(iterator2.next()); // { value: undefined, done: true }
// Map.keys(), Map.values(), Map.entries() 의 형태로도 사용 가능하다.
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
let a = map.keys();
console.log(a); // [Map Iterator] { 'a', 'b', 'c' }
console.log(a.next()); // { value: 'a', done: false }
console.log(a.next()); // { value: 'b', done: false }
console.log(a.next()); // { value: 'c', done: false }
console.log(a.next()); // { value: undefined, done: true }
const iterable = {
[Symbol.iterator]() {
let i = 3;
return {
next() {
return i == 0 ? { done: true } : { value: i--, done: false };
},
[Symbol.iterator]() { return this; } // Well Formed Iterable 이 되도록 자기 자신을 반환.
// 여기서 이터레이터가 자기 자신을 반환하는 심볼 이터레이터 메서드를 가지고 있을 때 이를 Well Formed Iterable 이라고 한다.
}
}
}
for (const a of iterable) console.log(a); // 3 2 1
일반 함수를 호출하면 return 문으로 반환값을 리턴하지만 제너레이터 함수를 호출하면 이터레이터를 반환한다.
// 일반 함수에서 앞에 *을 붙여서 제너레이터 함수를 만든다.
// 제너레이터는 이터레이터를 반환한다.
function *gen() {
yield 1;
yield 2;
yield 3;
return 100; // 순회할 때 return 값은 반영되지 않는다.
}
let iter = gen();
console.log(iter[Symbol.iterator]); // [Function: [Symbol.iterator]]
// 이터레이터는 심볼 이터레이터를 가지고 있다.
console.log(iter[Symbol.iterator]() === iter); // true
// 심볼 이터레이터의 실행 결과는 자기 자신이다.
console.log(iter); // Object [Generator] {}
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: 100, done: true }
console.log(iter.next()); // { value: undefined, done: true }
for (const a of gen()) console.log(a); // 1 2 3
// 제너레이터를 사용하게 되면, 제너레이터의 실행 결과가 이터러블이자 이터레이터이기 때문에 순회 가능.
※ 제너레이터를 통해 순회할 수 있는 값을 만들 수 있기 때문에 자바스크립트에서는 제너레이터를 통해서 어떠한 값이든 순회할 수 있게 만들 수 있다.
ES6가 되면서 변수, 상수 선언으로 let, const를 사용하고, for...of 문을 사용할 수 있는 등 여러가지 특징들에 대해 알고있고, 실제로 사용하고 있었지만, 이터러블, 이터레이터, 제너레이터에 대한 용어는 처음 들어 보았다. 특히 map을 for문으로 순회한적도 많았는데, 이렇게 순회하여 출력된 값이 map iterator 라는 것을 알게 된 후에 무엇을 사용하는 지도 모르는 채로 사용하고 있었다 라는 것을 느끼게 되었다.
지금까지는 모르고 for...of문 등을 사용했지만, 모르고 사용했다면 여기서 그칠 수 있는 기능들이고, 알고 사용한다면 더욱 유연하고 다양한 상황에서 사용할 수 있을것이라 생각한다.
오늘 배우긴 했지만 사실 이터러블, 이터레이터, 제너레이터에 대해 완전히 이해가 된 것은 아니다. 배움과 동시에 아직 모르는게 너무 많음을 알게 되었고, 오늘만 보고 끝내는 것이 아니라 다른 자료를 찾아보거나 꾸준히 공부하면서 완전하게 익혀야 되겠다는 생각이 들었다.
오늘 하루만에 끝날 내용은 아니라고 생각한다. 한 포스팅에 모두 정리하였지만, 내용을 이해하기 위해 차후 좀더 깊히 파고들어 하나씩이던 모두를 포함하던 날을 잡고 한 번 더 공부하면서 정리하는 과정이 필요하다고 생각한다.
https://ko.javascript.info/iterable
https://poiemaweb.com/es6-iteration-for-of