본문 바로가기

programming/Javascript

[Javascript] (iterable 프로토콜, iterator 프로토콜) of [iteration 프로토콜]

ES6에서 도입됨

이터러블은 데이터 공급자의 역할

다양한 이터러블이 각자의 순회방식을 갖는다면, 데이터 소비자는 모든 방식을 각각 지원해야한다.

효율성을 위해 프로토콜(규칙)을 정의

이터러블은 Lazy evaluation(지연평가)를 통해 값을 생성한다. 

100개의 값이 필요할때 처음부터 100개를 만드는 것이아니고, 하나씩 next -> next-> next로 하나씩 만든다.

할당이 이루어지기 전까지는 데이터를 생성하지 않는다.

 

iteration 프로토콜

데이터 컬렉션을 순회하기 위한 프로토콜(규칙)

이터레이션 프로토콜(규칙)을 준수한 객체는 for문으로 순회할 수 있고, spread문법으로 펼칠 수 있다.

이터레이션 프로토콜 (대분류) 안에 이터러블 프로토콜과 이터레이터 프로토콜이 있다.

 

 

iterable 프로토콜

- 순회 가능한 자료구조 (for 문)

- Spread 문법 적용가능 

- Symbol.iterator 메소드를 소유한다 -> iterator 를 리턴한다.

- iterable 프로토콜(규칙)을 준수한 객체를 iterable 이라 한다

 

interable 객체 예시 : Array

const array = [1, 2, 3];

// 배열은 Symbol.iterator 메소드를 소유한다.
// 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다.
console.log(Symbol.iterator in array); // true

// 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for (const item of array) {
  console.log(item);
}

[실행결과]

 

iterable 하지 않은 객체 예시 : Object

const obj = { a: 1, b: 2 };

// 일반 객체는 Symbol.iterator 메소드를 소유하지 않는다.
// 따라서 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다.
console.log(Symbol.iterator in obj); // false

// 이터러블이 아닌 일반 객체는 for...of 문에서 순회할 수 없다.
// TypeError: obj is not iterable
for (const p of obj) {
  console.log(p);
}

[실행결과]

t

 

iterator 프로토콜

- next 메소드 소유 -> (value, done) 프로퍼티를 갖는 iterator result 객체를 리턴한다.

- next 메소드는 이터러블의 각 요소를 순회하기 위한 포인터의 역할

- iterator 프로토콜(규칙)을 준수한 객체를 iterator 이라 한다

 

const array = [1, 2, 3];

// 배열은 Symbol.iterator 메소드를 소유한다.
// 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다.
console.log(Symbol.iterator in array); // true

// 이터러블 프로토콜을 준수한 배열은 for...of 문에서 순회 가능하다.
for (const item of array) {
  console.log(item);
}

// Symbol.iterator 메소드는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();

// iterator는 next 메소드를 갖는다.
// next 메소드는 value, done 속성을 갖는 Result객체를 리턴한다
let iteratorResult = iterator.next();
console.log(iteratorResult); // {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}

[실행결과]

 

Javascript ES6에서 제공하는 내장 이터러블

Array, String, Map, Set, TypedArray, DOM data structure, Arguments

Symbol 객체의 프로퍼티 중 length와 prototype을 제외한 프로퍼티들을 Well-Known Symbol 이라 부른다

이미지출처: 모던 자바스크립트 Deep Dive

이런 Well-Known Symbol 은 자바스크립트 엔진에 "상수"로 존재하며, 자바스크립트 엔진은 각각 이 상수들을 참조하여 일정한 처리를 한다.

예를 들어 어떤 객체가 Symbol.iterator 를 프로퍼티로 갖고 있으면(메소드로 가지고 있으면) 이 객체는 이터레이션 프로토콜을 따는 것으로 생각하고 이터레이터로 동작시킨다. -> 이터레이터를 커스텀하여 만들 수 있다!

Javascript 내장 이터러블들은 그럼, Symbol.iterator를 프로퍼티로 이미 갖고 있다는 뜻

 

* Symbol 은 ES6에서 추가된 원시 데이터 유형 (기존: Boolean, Null, Undefined, Number, String, +Symbol)

* Symbol("abc") 같은 문자열을 할당해도 다르게 인식된다, 고유한 존재

* Object에서 키값으로 Symbol을 사용하는 경우 : String과의 차별성, 객체에 같은 속성을 string으로 추가시, 에러가난다. Symbol로 추가시 충돌이 없다. 

 

for...of문

우리가 자주 사용하던 for문도 내부적으로 이터레이터의 next 메소드를 호출하여 이터러블을 순회하고, next 메소드가 반환하는 result 객체의 value 속성 값을 변수에 할당한다. 그리고 done 속성 값이 true가 될때까지 순회한다.

 

for문의 내부 동작 방식을 코드로 표현해보면 다음과 같다

// 이터러블
const iterable = [1, 2, 3];

// 이터레이터
const iterator = iterable[Symbol.iterator]();

for (;;) {
  // 이터레이터의 next 메소드를 호출하여 이터러블을 순회한다.
  const res = iterator.next();

  // next 메소드가 반환하는 이터레이터 리절트 객체의 done 프로퍼티가 true가 될 때까지 반복한다.
  if (res.done) break;

  console.log(res);
}

 

커스텀 이터러블

1) 이터러블 객체 만들기

const fibo = {
    [Symbol.iterator]() {
        let [pre, cur] = [0,1];
        const max = 10; // 10까지 피보나치

        // Symbol.iterator 메소드(지금 정의하는 메소드)는 iterator를 반환해야함
        // iterator는 next 메소드를 가져야함
        // next 메소드는 { value, done } 객체를 반환해야함
        return {
            next() {
                [pre, cur] = [cur, pre+cur];
                return {
                    value : cur,
                    done : cur >= max
                }
            }
        }
    }
}

for (const num of fibo) {
    console.log(num)
}

// spread 문법
console.log([...fibo])

// 디스트럭처링
const [first, second, ...rest] = fibo;
console.log(first, second, rest)

[실행 결과]

 

2) 이터러블을 생성하는 함수 만들기

function fiboIterableMaker(max) {
    let [pre, cur] = [0, 1];
    // Symbol.iterator 메소드(지금 정의하는 메소드)는 iterator를 반환해야함
    // iterator는 next 메소드를 가져야함
    // next 메소드는 { value, done } 객체를 반환해야함
    return {
        [Symbol.iterator]() {
            return {
                next() {
                    [pre, cur] = [cur, pre+cur];
                    return {
                        value : cur,
                        done : cur >= max
                    }
                }
            }
        }
    }
}

// 이터러블을 반환하는 함수에 이터러블의 최대값을 전달한다.
console.log(fiboIterableMaker(10))

for (const num of fiboIterableMaker(10)) {
    console.log(num); // 1 2 3 5 8
}

[실행결과]

 

3) 이터러블이면서 이터레이터 객체를 생성하는 함수 만들기

// 이터러블 & 이터레이터 객체를 반환
// 이터러블이려면 Symbol.iterator 메소드 가져야함
// 이터레이터는 next 메소드 가져야함
// next는 value, done 리절트 리턴해야함

function fiboIterableIterator(max){
    let [pre, cur] = [0,1]
    return {
        [Symbol.iterator](){
            return this; // 지금의 이터레이터를 리턴
        },
        next() {
            [pre, cur] = [cur, pre+cur];
            return {
                value : cur,
                done : cur >= max
            }
        }
    }
}

// iter는 이터러블
let iter = fiboIterableIterator(10);
console.log('iter', iter) //iter {next: ƒ, Symbol(Symbol.iterator): ƒ}

for (const num of iter) { 
    console.log(num) // 1,2,3,5,8
}

// iter는 이터레이터
console.log(iter.next()); //{value: 21, done: true}
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

[실행결과]