[Javascript] Iterator, Generator

Bam·2022년 3월 1일
0

Javascript

목록 보기
88/106
post-thumbnail

Iterator

Iterator(반복자)는 반복 처리가 가능한 객체를 의미합니다. 좀 더 쉽게 이야기 하자면, 열거 가능한 객체들이 각 요소에 대해 반복 처리가 가능하죠? 이런 열거 가능한 객체들을 반복자라고 합니다. 대표적으로 Array, String, Set, Map 등이 반복자를 가지고 있습니다. 반복자를 가지고 있는 객체들의 특징은 for ~ of문을 통해 요소의 열거가 가능하다는 점 입니다.

next

Iteratornext()메소드를 갖습니다. 이 메소드는 반환값으로 done과 value를 가지고 있는 객체를 반환합니다.
done은 객체의 반복이 끝났는지에 대한 boolean 형 값을 의미합니다. 반복이 끝났다면 done은 true를 가지고, 끝나지 않았다면 false를 갖습니다.
value는 객체로부터 현재 가져온 값을 반환합니다.

let arr = [1, 2, 3];
//[Symbol.iterator]() 메소드는 반복자를 반환합니다.
let itr = arr[Symbol.iterator]();
let elem;

while(elem = itr.next()) {
    if (elem.done) {
        break;
    }

    console.log(elem.done);
    console.log(elem.value);
}

[Symbol.iterator]라는 구문이 보이는데, 이 구문은 Iterator 객체를 반환해줍니다. 이 객체가 next 메소드를 가지고 있으며, 이 메소드를 통해 반복자 객체의 값을 활용할 수 있습니다. 그리고 이 중에서 값을 쉽게 취득할 수 있도록 생긴 구문이 바로 for ~ of문인 것 입니다. 그래서 아까 위에서 반복자를 가진 객체들은 for ~ of를 사용할 수 있다라고 했었던 것 입니다.

[Symbol.iterator]에서 프로퍼티에 대괄호[]가 온 것이 무슨 문법인가 하시는 분들이 계실텐데요. 이 문법은 객체 확장 표현식에서 다룬 계산된 프로퍼티(computed property names)구문입니다. 복습하자면, 대괄호 내에 자바스크립트 표현식을 넣으면, 해당 표현식이 프로퍼티의 키로 사용될 수 잇게 해주는 것 입니다. 여기서는 Symbol.iterator가 하나의 프로퍼티 키로 사용되었음을 의미합니다.

사용자 정의 객체에 Iterator 적용

사용자 정의 객체에 대해서도 Iterator로 만들 수 있습니다. 방금 전에 본 [Symbol.iterator]를 정의하기만 한다면 사용자 정의 객체도 반복자로 만들 수 있습니다.

class HandmadeItr {
    constructor(data) {
        this.data = data;
    }

    //반복자 정의
    [Symbol.iterator]() {
        let current = 0;
        let that = this;
        
        return {
            next() {
                return current < that.data.length ?
                  {value: that.data[current++], done: false} : {done: true};
            }
        };
    }
}

let itr = new HandmadeItr([1, 2, 3]);

for (let value of itr) {
    console.log(value);
}


사용자 정의 객체인데도 불구하고 for ~ of문이 잘 작동하죠? 제대로 반복자가 되었음을 알 수 있습니다.

위 코드에서 다음 부분이 반복자를 정의하는 코드입니다.

[Symbol.iterator]() {
        let current = 0;
        let that = this;

        return {
            next() {
                return current < that.data.length ?
                  {value: that.data[current++], done: false} : {done: true};
            }
        };
    }

여기서 주목할 것은 that입니다. 왜 this를 따로 that에 저장하고 사용했냐면, 메소드 내부의 this는 호출한 객체를 가리킵니다. 따라서 next 메소드 내부에서 this를 사용하면 [Symbol.iterator]를 가리키게됩니다. 하지만 우리에게 필요한 건 this가 우리가 만든 객체를 가리켜야합니다.
그렇기 떄문에 [Symbol.iterator]에서 this를 쓰면 우리가 만든 객체를 가리키므로, next 메소드에 들어가기전에 this를 호출해서 따로 that이라는 이름으로 따로 담아두고 사용하는 것 입니다. 이렇게 하면 next 메소드 내부에서 객체에 접근할 수 있게 됩니다.

그리고 마지막에 반복자는 next 메소드를 반환하는데, 이 next 메소드는 또 done과 value로 이루어진 객체를 반환합니다. 위에서 next 메소드의 반환값이 done과 value로 이루어진 객체라고 했던 것 기억하시죠?

이렇게 사용자 정의 객체에 반복자를 직접 정의함으로써 사용자 정의 객체를 반복자 객체로 만들 수 있게됩니다.

Generator

바로 위에서 사용자 정의 객체를 반복자로 만드는 것을 보았는데, 솔직히 복잡하죠? 그래서 반복자 객체를 쉽게 만들기 위해 Generator(발생자)가 등장했습니다.

Generator는 반복자 객체를 쉽게 만들 수 있게 해줍니다. Generator는 다음과 같이 생겼습니다.

function* gen() {
    yield 'Ge';
    yield 'nera';
    yield 'tor'
}

for (let value of gen()) {
    console.log(value);
}


function*yield라는 것이 등장했는데, 이 두 가지에 대해서 알아보겠습니다.

function*은 발생자 정의를 위한 함수 키워드입니다. *를 반드시 붙여야 이 함수가 발생자 선언을 한다고 스크립트가 해석할 수 있으니 잊으시면 안됩니다.

yield는 발생자에서 값을 반환하는 명령입니다. 근데 함수에서 값을 반환하는 것으로는 return도 있고 그동안 return을 사용해왔습니다. 두 명령의 차이는 무엇일까요?
reutrn은 값을 반환하고 함수를 종료시킵니다. 그래서 재호출하는 경우 함수가 처음부터 다시 실행됩니다. 반면 yield는 값을 반환하고 함수를 일시정지시킵니다. 그래서 함수를 재호출하면 정지했던 위치 다음 줄부터 함수가 다시 실행이 됩니다. 그래서 위의 예시에서 Ge Ge Ge가 아니라 Ge nera tor이라고 출력이 된 것 입니다.

그러면, 발생자를 이용해서 사용자 정의 객체의 반복자를 정의해보겠습니다.

class HandmadeItr {
    constructor(data) {
        this.data = data;
        this[Symbol.iterator] = function* () {
            let current = 0;
            let that = this;

            while(current < that.data.length) {
                yield that.data[current++];
            }
        }
    }
    }


let itr = new HandmadeItr([1, 2, 3]);

for (let value of itr) {
    console.log(value);
}

0개의 댓글