해당 포스팅은 위키북스의 모던 자바스크립트 Deep Dive라는 책을 독학하며 기록하는 글입니다.

이터레이션 프로토콜

ES6에서 도입된 이터레이션 프로토콜은 순회 가능한 데이터 컬레션(배열, 문자열, 유사 배열 객체, DOM 컬레션 등)을 만들기 위해 ECMAScript 사양에 정의하여 미리 약속한 규칙이다.
이터레이션 프로토콜에는 이터러블 프로토콜이터레이터 프로토콜이 있다.

이터러블 프로토콜

Symbol.iterator를 프로퍼티 키로 사용한 메서드르 직접 구현하거나 프로토타입 체인을 통해 상속받은 Symbol.iterator메서드를 호출하면 이터레이터를 반환하는데 이러한 규약을 이러터블 프로토콜이라 한다. 해당 프로토콜을 준수한 객체를 이터러블이라 하며 이터러블은 for...of문으로 순회할 수 있고 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.

이터레이터 프로토콜

이터러블 프로토콜을 준수한 객체인 이터러블 객체가 Symbol.iterator메서드를 호출하면 이터레이터가 반환되는데, 해당 이터레이터는 next메서드를 가지고 있고, next메서드를 호출하면 이터러블 객체를 순회하며 valuedone프로퍼티를 갖는 이터레이터 리절트 객체를 반환한다. 이러한 규햑을 이터레이터 프로토콜이라 한다.

이터러블

이터러블은 이터러블 프로토콜을 준수한 객체를 말하며 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 상속받은 객체이다.

자바스크립트에서는 이터레이션 프로토콜을 준수한 객체인 빌트인 이터러블을 제공하는데 다음과 같다.

  1. Array
  2. String
  3. Map
  4. Set
  5. TypedArray
  6. arguments
  7. DOM 컬렉션

이터레이터

이터러블 객체에서 Symbol.iterator를 프로퍼티키로 사용한 메서드를 호출하면 반환되는 것으로 next메서드를 갖는다.

이터레이터에서 next메서드를 호출하면 이터레이터 리절트 객체를 반환하는데 해당 객체는 현재 순회하고 있는 요소의 값인 value 프로퍼티와 순회가 끝났는지를 담고 있는 done 프로퍼티를 가지고 있다.

const arr = [1, 2, 3];

const iterator = arr[Symbol.iterator]();

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...of

for...of 문은 for...in 문과 사용법이 비슷하며 기능도 비슷하다.

for...in 문의 경우 for(변수선언문 in 객체) {}를 통해 객체의 프로퍼티 키가 하나씩 변수에 할당되며 반복되는 반복문이었다면 for...of 문의 경우 for(변수선언문 of 객체) {}를 통해 객체의 프로퍼티 키에 해당하는 값들이 하나씩 변수에 할달되며 반복되는 반복문이다.

여기서 엄밀히 따지면 for...of 문의 반복되는 대상에 들어가야 하는건 객체가 아니라 이터러블이다. 왜냐하면 for...of 문은 내부적으로 이터레이터의 next메서드를 한 번씩 실행시켜 얻어지는 이터레이터 리절트 객체의 value값을 변수에 할당하는 것이기 때문이다. 그리고 이터레이터 이절트 객체의 done값이 true가 나오면 순회를 종료하는 프로세스를 가지고 있다.

이터레이션 프로토콜의 필요성

이러터블은 for...of 문, 스프레드 문법, 배열 디스트럭처링 할당과 같은 데이터 소비자에 의해 사용되므로 데이터 공급자 역할을 한다고 할 수 있다.

만약 다양한 데이터 공급자가 각자의 순회 방식을 갖는다면 데이터 소비자는 다양한 데이터 공급자의 순회 방식에 맞춰 다양한 방식을 모두 지원해야 하는데 이는 효율적이지 않다. 따라서 다양한 데이터 공급자가 이터레이션 프로토콜을 준수하도록 규정하면 데이터 소비자는 이터레이션 프로토콜만을 지원하도록 구현하면 되기 때문에 효율적이다.

즉, 이터레이션 프로토콜은 다양한 데이터 공급자가 하나의 순회 방식을 갖도록 규정하여 데이터 소비자가 효율적으로 다양한 데이터 공급자를 사용할 수 있도록 데이터 소비자와 데이터 공급자를 연결하는 인터페이스 역할을 한다. - p621 마지막 문단

사용자 정의 이터러블

이제 이터레이션 프로토콜이 뭔지 알았으니 해당 프로토콜만 준수한다면 사용자도 커스텀한 이터러블을 만들 수 있다.

다시 한 번 이터러블이 준수해야 하는 사항들을 알아보자.

  1. Symbol.iterator를 프로퍼티 키로 갖는 메서드를 가지고 있어야 한다.
  2. 1의 메서드를 호출하면 이터레이터를 반환해야 한다.
  3. 2에서 반환한 이터레이터는 next메서드를 가지고 있어야 한다.
  4. 3의 next메서드를 호출하면 이터레이터 리절트 객체를 반환해야 한다.
  5. 4의 이터레이터 리절트 객체는 value와 done프로퍼티를 가지고 있어야 한다.

즉, 한 줄로 설명하면 value과 done프로퍼티를 갖는 이터레이터 리절트 객체를 반환하는 next메서드를 갖는 이터레이터를 반환하는 메서드를 Symbol.iterator를 프로퍼티 키로 하는 프로퍼티의 값으로 생성해주면 된다.

다음 예제를 통해 피보나치 수열을 반환하는 이터러블을 보자.

const fibonacciFunc = function(max) {
  let [pre, cur] = [0, 1]; // 배열 디스트럭처링 할당으로 다음에 살펴보자.
  
  return {
    // return을 this로 하면 굳이 Symbol.iterator를 통해 이터레이터를 취득하지 않아도 된다. 즉, 이러터블이면서 이터레이터인 객체를 만들 수 있다.
    [Symbol.iterator]() { retrun this; }
    
    next() {
      [pre, cur] = [cur, pre + cur];
      return { value: cur, done: cur >= max };
    }
  };
};

iter = fibonacciFunc(10); // iter는 이터러블이면서 이터레이터인 객체이다.

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}

지연 평가

위에서 만든 피보나치 수열의 경우 max값을 인수로 받아 최대값을 지정했지만 max을 지정하지 않는 경우 피보나치 무한 수열을 만들 수있다.

보통의 데이터 공급자인 배열이나 문자열들은 모든 데이터를 메모리에 미리 확보한 다음 데이터를 공급한다. 하지만 위 예제를 통해 만들어진 무한 피보나치 수열의 경우 next메서드가 호출되는 시점에 다음 데이터를 생성하는 지연 평가가 이루어진다.

이는 데이터가 필요한 시점 이전까지는 미리 데이터를 생성하지 않다가 데이터가 필요한 시점이 되면 그때야 비로소 데이터를 생성하는 기법이다. 따라서 빠른 실행 속도를 기대할 수 있고 불필요한 메모리를 소비하지 않으며 무한도 표현할 수 있다는 장점이 있다.

profile
I Will be Relaxed Person

0개의 댓글