Iteration
이터레이션 프로토콜에 대해서 공부해보자.

이터레이션 프로토콜
데이터 콜렉션을 순회하기 위한 약속된 규칙을 의미한다.
위 규칙을 준수하는 객체는 for..of 로 순회하거나 spread 문법의 피연산자가 될 수 있다.
iterable, iterator 에 해당하는 규칙이 존재한다.
iterable
이터러블 프로토콜을 준수한 객체를 이터러블이라 한다.
Symbol.iterator 메소드를 구현하거나, 프로토 타입 체인에 의해 상속한 객체를 의미한다.
배열은 기본적으로 Symbol.iterator 메소드를 소유하므로, 이터러블 프로토콜을 준수한다.
const array = [1,2,3]; console.log(Symbol.iterator in array); for (const item of array){ console.log(item); } /* 일반 객체는 이터레이션 프로토콜을 준수하지 않는다. Symbol.iterator 를 직접 메소드를 커스텀구현하면 일반 객체도 가능은 하다. */ const objectLiteral = { a: 10, b: 20}; console.log(Symbol.iterator in objectLiteral); // false for(const oL of objectLiteral){ console.log(p); }
빌트인 이터러블
크게 배열, 문자열, 콜렉션에 해당하는 객체라면 이미 내장이 되어있는 이터러블이 존재한다.
이터레이션 프로토콜의 필요성
빌트인 이터러블 그리고 Symbol.iterator 를 커스텀 구현한 이터러블은 데이터 공급자의 역활을 한다.
각기 다양한 데이터 자원들이 독자적인 순회 방식을 가질경우 데이터 소비자는 그에따른 데이터의 순회 방식을 모두 지원해야한다.
규칙을 정해서 데이터 소비자와 데이터 공급자간의 연결고리 역활을 하는 인터페이스라고 보면 된다.
iterator
iterator 를 구현하는 규칙은 다음과 같다.
next() 소유
next() 호출시, iterable 을 순회하며 value, done 프로퍼티를 가지는 iterator result 객체를 반환하는 것을 의미.
커스텀 이터러블의 구현 방법
예시 코드를 보며 살펴보면 다음과 같다.
/* Symbol.iterator 의 내부 변수는 외부로 전달이 불가능하다. 이 특성을 이용하여 다양한 구현이 가능하다. Symbol.iterator 메소드는 next 메소드를 소유한 이터레이터를 반환해야 한다. next 메소드는 이터레이터 리절트 객체를 반환하며 순회시 마다 호출이 된다. */ const fibonacci = { [Symbol.iterator]() { let [pre, cur] = [0, 1]; const max = 10; //return iterator return { next() { [pre, cur] = [cur, pre + cur]; return { value: cur, done: cur >= max }; } }; } }; // 반복문 에서의 사용 for (const num of fibonacci) { console.log(num); } // spread 문법 const arr = [...fibonacci]; console.log(arr); // [ 1, 2, 3, 5, 8 ] // destructuring const [first, second, ...rest] = fibonacci; console.log(first, second, rest); // 1 2 [ 3, 5, 8 ]
위 예제의 피보나치 이터러블에는 외부에서 값을 전달할 방법이 없다는 점이 있으나, 다음과 같이 하면 이터러블을 반환하는 구조를 취하면 특정한 값을 외부에 전달할 수 있다.
const fibonacciFunc = function (max) { let [pre, cur] = [0, 1]; return { [Symbol.iterator]() { return { next() { [pre, cur] = [cur, pre + cur]; return { value: cur, done: cur >= max }; } }; } }; }; for (const num of fibonacciFunc(10)) { console.log(num); }
이터레이터를 생성하려면 이터러블의 Symbol.iterator 메소드를 호출해야 한다. 이터러블이면서 이터레이터인 객체를 생성하면 Symbol.iterator 메소드를 호출하지 않아도 된다.
const fibonacciFunc = function (max) { let [pre, cur] = [0, 1]; /* Symbol.iterator 메소드와 next 메소드를 소유한 이터러블이면서 이터레이터인 객체를 반환 하려면 위 예지와 다르게 감싸는 구조가 아닌 별도의 프로퍼티로 구성하면 된다. */ return { [Symbol.iterator]() { return this; }, next() { [pre, cur] = [cur, pre + cur]; return { value: cur, done: cur >= max }; } }; }; let iter = fibonacciFunc(10); // 이터레이터로 활용되는 코드 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: 5, done: false} console.log(iter.next()); // {value: 8, done: false} console.log(iter.next()); // {value: 13, done: true} iter = fibonacciFunc(10); // 이터러블로 활용되는 코드 for (const num of iter) { console.log(num); }
무한 이터러블, 지연평가(Lazy evaluation) 를 통한 활용법
무한한 이터러블을 생성하는 함수를 정의해보는 것과 그 사용법을 알아보자.
이터러블의 특성 중 지연평가를 이용하여 필요한 값만 취득하는 예제이다.
방법은 간단하다. next()의 done 프로퍼티를 생략하면 된다.
지연평가는 데이터를 소비하거나, 디스트럭처링 할당이 되기 이전까지 데이터를 생성하지 않는다.
const fibonacciFunc = function () { let [pre, cur] = [0, 1]; return { [Symbol.iterator]() { return this; }, next() { [pre, cur] = [cur, pre + cur]; return { value: cur }; } }; }; // 제약을 걸지 않으면 무한히 생성된다. for (const num of fibonacciFunc()) { if (num > 10000) break; console.log(num); } // 원하는 값만 취득하는 방식 const [f1, f2, f3] = fibonacciFunc(); console.log(f1, f2, f3); // 1 2 3
Last updated
Was this helpful?