반복 가능한 객체(iterable object)는 for...of 구문과 함께 ES2015에서 도입되었다.
반복 가능한 객체를 다른 객체와 구분짓는 특징은
객체의 Symbol.iterator
속성에 특별한 형태의 함수가 들어있다는 것!
let foo = "hello world"; console.log(foo[Symbol.iterator]); // ƒ [Symbol.iterator]()
객체의 Symbol.iterator
속성에 특정 형태의 함수가 들어있다면, 이를 반복 가능한 객체(iterable object) 혹은 줄여서 iterable이라 부르고
"해당 객체는 iterable protocol을 만족한다"고 말합니다.
✅ iterable 객체를 만들어내는 내장된 생성자
✅ 어떤 객체가 Iterable이라면, 그 객체에 대해서 아래의 기능들을 사용할 수 있다.
for...of
루프(...)
Iterable을 구현하는 가장 쉬운 방법은 ES2015에 도입된 generator 함수를 사용하는 것!
Generator 함수는 iterable 객체를 반환하는 특별한 형태의 함수이다.
// generator 함수 선언하기 function* foo1() { // ... } // 표현식으로 사용하기 const foo2 = function* () { // ... } // 메소드 문법으로 사용하기 const obj = { * foo3() { // ... } }
Generator 함수를 호출하면 객체가 생성되는데, 이 객체는 iterable protocol을 만족한다.
즉, Symbol.iterator
속성을 갖고 있다는 뜻이다.
function* foo1() {}; let iterable = foo1(); console.log(iterable[Symbol.iterator]) // ƒ [Symbol.iterator]()
Generator 함수 안에서는 yield
라는 특별한 키워드가 있다.
Generator 함수 안에서 yield 키워드는 return과 유사한 역할이며, iterable의 기능을 사용할 때 yield 키워드 뒤에 있는 값들을 순서대로 넘겨준다.
function* foo4() { yield 1; yield 2; yield 3; } // 1, 2, 3이 순서대로 출력. for (let n of foo4()) { console.log(n); }
yield*
표현식을 사용하면, 다른 generator 함수에서 넘겨준 값을 대신 넘겨줄 수도 있다.
function* foo4() { yield 1; yield 2; yield 3; } function* foo5() { yield* foo4(); yield* foo4(); } // 1, 2, 3, 1, 2, 3이 순서대로 출력. for (let n of foo5()) { console.log(n); }
그렇다면 왜 제네레이터를 사용하는가?
그 이유는 바로 느긋한 평가(lazy evaluation) 때문이다.
프로그래밍에서 느긋한(lazy)이라는 단어는 대개 어플리케이션의 성능에 대해 긍정적인 효과를 가지고 있다.
보통 함수를 호출하면 즉시 함수가 실행되고 종료된다.
function normalFunc(num){ const arr = []; for(let i=0; i<num; i++){ arr.push(i); } return arr; } console.log(normalFunc(3)); // [0, 1, 2]
하지만 제네레이터 함수를 호출하면
- 제네레이터 객체를 반환하고,
- 제네레이터 객체는 함수와
yield
문을 조합해서- 일시정지/재개 컨트롤을 할 수 있다.
function* generator(num){ const arr = []; for(let i=0; i<num; i++){ arr.push(i); yield arr; } } const gen = generator(3); console.log(gen); // Iterator [Generator] {} gen.next(); // { value: [ 0 ], done: false } gen.next(); // { value: [ 0, 1 ], done: false } gen.next(); // { value: [ 0, 1, 2 ], done: false }
코드를 보면
💡 normalFunc()
는 호출되면 인자로 받은 숫자만큼의 길이의 배열을 즉시 생성해서 반환하고,
💡 generator()
는 호출되면 제네레이터 객체를 반환하고, 제네레이터 객체의 next()를 호출할 때마다 길이가 1씩 늘어나는 배열을 반환한다.
그래서 어디에 쓰이며 어느 부분에서 메모리 효율이 좋아지는 걸까?
인스타그램이나 페이스북은 스크롤을 페이지의 끝까지 내리면
계속해서 게시글들이 로드되어 화면에 보여지는데,
제네레이터 안에 while(true)같은 무한 반복문 내에 데이터 요청-렌더링 코드를 넣어두고, 페이지가 끝나면 next()를 사용해 데이터를 요청하고 렌더링 해주는 것이다.(참고)