정보실

웹학교

정보실

javascript JavaScript 이터레이터 및 생성기 : 동기식 이터레이터

본문

소개 


가장 먼저 지적해야 할 것은 JavaScript의 반복은 프로토콜을 기반으로 한다는 것입니다. 즉, 언어의 인터페이스였던 인터페이스를 인터페이스 지원으로 대체하는 규칙 세트입니다. 어쨌든 ECMAScript 사양은 주로 "인터페이스"라는 단어를 사용하므로 동일한 작업을 수행합니다.


https://dev.to/jfet97/javascript-iterators-and-generators-synchronous-iterators-141d 


이 개념이 새로운 사람이라면 인터페이스에서 코드의 두 엔티티 간의 계약으로 인터페이스를 상상해야 합니다. 

계약이 없으면 이 두 부분은 서로를 너무 많이 알고 서로 의존하게 됩니다. 하나의 내부 변화는 아마도 다른 하나를 바꾸게 할 것입니다.


반복자로 돌아가서 주된 목적은 주어진 컬렉션의 각 요소를 사용할 수 있도록 하는 것입니다. 소비자는 컬렉션이 이러한 요소를 어떻게 저장하고 관리하는지 알 필요가 없습니다. 반복자 인터페이스가 존중되는 한 소비자와 컬렉션은 완전히 독립적으로 유지됩니다. 소비자에게 영향을 주지 않고 컬렉션의 내부 세부 정보를 변경할 수 있습니다. 

실제로 전체 컬렉션을 다른 컬렉션으로 바꿀 수 있습니다. 동시에 컬렉션은 소비자에 대해 아무것도 모릅니다. 따라서 서로 매우 다른 소비자가 반복 할 수 있습니다.


Iterable, Iterator 및 IteratorResult 인터페이스 


전체 반복이 기반으로 하는 세 가지 인터페이스가 있습니다.


The Iterable 


Iterable 인터페이스는 엔티티가 iterable로 간주하기 위해 구현해야 하는 것을 정의합니다. 사양에는 iterable에 Iterator 인터페이스를 구현하는 객체를 반환하는 @@ iterator 메소드라는 필수 메소드가 있다고 말합니다. @@ iterator 란 무엇입니까? Symbol 생성자에서 찾을 수 있는 특정 Symbol입니다 : Symbol.iterator.

const Iterable = { 
    [Symbol.iterator]() {
        return Iterator;
    } 
}


The Iterator 


Iterator 인터페이스는 엔티티가 반복자로 간주되도록 구현해야 하는 것을 정의합니다. 이 스펙에는 반복자에는 다음과 같은 필수 메소드가 있습니다. 이 메서드, 즉 반복 자체의 받침대는 IteratorResult 인터페이스를 구현하는 객체를 반환합니다. 주요 목적은 더 이상 항목이 없을 때까지 컬렉션에서 후속 요소를 가져 오는 것입니다.


return 메소드와 throw 메소드라는 두 가지 다른 선택적 메소드가 있습니다. 값을 반환하면 IteratorResult 여야 합니다. 전자의 목적은 반복자가 종료에 도달하지 않더라도 소비자가 더 이상 다음 메소드를 호출하지 않을 것임을 반복자에게 알리는 것입니다. 이런 식으로 반복자가 필요한 정리 작업을 수행 할 수 있습니다. 후자의 목적은 소비자에게 오류가 발생했음을 반복자에게 알리는 것입니다.


세 가지 방법 모두 하나 이상의 인수를 허용합니다.

  • 다음 방법은 하나 이상의 인수를 수신 할 수 있어야 하지만 사양에서 "그들의 해석과 유효성은 대상 반복자에 따라 다릅니다" 라고 합니다.
  • 리턴 메소드는 수신 된 인수를 리턴하여 리턴 된 IteratorResult에 삽입해야 합니다.
  • throw 메소드는 일반적으로 예외 인 수신 인수를 throw해야 합니다.

호출하기 전에 마지막 두 가지 방법이 있는지 확인하십시오!


const Iterator = {
    next() { 
       return IteratorResult;
    },
    return() {
       return IteratorResult;
    },
    throw(e) {
        throw e;
    }
}


The IteratorResult 


IteratorResult 인터페이스는 엔티티가 일반 반복 단계의 올바른 결과로 간주되도록 구현해야 하는 사항을 정의합니다.


사양에 따르면 반복자 결과에는 놀랍게도 선택적 필드 인 완료 필드와 값 필드의 두 필드가 있습니다. done 필드는 반복의 끝을 알리는 부울 플래그입니다. 반복자가 리턴 한 값이 유효하고 마지막 유효 값이 리턴 된 후 true가 되는 한 false 여야 합니다. done 플래그가 생략되면 값이 false 인 것으로 간주됩니다. 값 필드는 유효한 ECMAScript 값을 포함 할 수 있습니다. done 플래그가 true가 될 때까지 각 반복 단계의 현재 반복 값입니다. 그 후, 이터레이터가 제공된 경우 이터레이터의 리턴 값입니다. 값 플래그를 생략하면 값이 정의되지 않은 것으로 간주됩니다.


const IteratorResult = { 
    value: any, 
    done: boolean, 
}


Conventions 


올바른 반복자를 작성하기 위해 따라야 하는 몇 가지 일반적인 규칙이 있습니다.

  1. 각 Iterable 엔티티는 @@ iterator 메소드가 호출 될 때마다 새로운 새 반복자를 리턴해야 합니다.
  2. 각 Iterator 엔터티도 Iterable이어야 하며 (나중에 자세히 설명) 간단히 이 함수를 반환하는 함수, 즉 iterator 자체에 대한 참조로 @@ iterator 메서드를 구현해야 합니다. 이것은 이전 규칙의 통제 된 예외입니다.
  3. done 플래그를 false로 설정하여 각 반복 단계의 유효한 값을 명시적으로 보고 하십시오. 관련 값을 done : true와 결합하지 마십시오.
  4. return 메소드 또는 throw 메소드가 호출되면 반복자는 소진 된 것으로 간주되고 다음 메소드 호출은 더 이상 유효한 값을 리턴하지 않아야 합니다. 어쨌든 현재 return / throw 호출이 값을 반환하면 done : true와 결합되어야 합니다.
  5. 마지막 값이 리턴 된 후 다음 메소드는 {value : undefined, done : true}를 리턴해야 합니다. 오류를 던지지 마십시오. 그 후 다음 메소드가 여러 번 호출 된 경우에도 표시된 대로 반복 종료 신호를 보내기만 하면 됩니다.

Default iterators 


언어에 이미 존재하는 가장 일반적인 반복자 중 하나를 사용하는 방법을 살펴 보겠습니다. 배열은 기본적으로 @@ iterator 메소드를 구현하기 때문에 반복자를 배열에서 반복하는 것이 매우 간단합니다.

const array = [1, 2, 3];

// get a fresh, new iterator to iterate over the array
const iterator = array[Symbol.iterator]();


// remember: we have to call the 'next' method to iterate

iterator.next(); // { value: 1, done: false } 
iterator.next(); // { value: 2, done: false } 
iterator.next(); // { value: 3, done: false } 
iterator.next(); // { value: undefined, done: true } 
iterator.next(); // { value: undefined, done: true }


앞에서 언급 한 몇 가지 규칙이 어떻게 준수 되는지 확인할 수 있습니다.

  • 마지막 유효 값 (3)은 done : true가 아니라 done : false와 결합됩니다.
  • 오류가 발생하지 않았음을 보여주기 위해 의도적으로 범위를 벗어났습니다. 대신 우리는 항상 {value : undefined, done : true}

배열은 유일한 내장 이터러블이 아닙니다. 문자열 객체, TypedArray, 맵 및 세트도 이터러블입니다. 프로토 타입 객체는 각각 @@ iterator 메소드를 구현하기 때문입니다.


Using iterators 


iterable과 콜백을 취하는 함수를 생성하여 후자의 각 반복자 결과 값에 전달합니다.


function iterateOver(iterable, cb) {

    // let it throw if the iterable argument is not an iterable
    const iterator = iterable[Symbol.iterator]();

    // it will contains the iterator result of each step
    let iteratorResult;

    // starts the iteration
    iteratorResult = iterator.next();

    // do the while loop only for values coupled with 'done:false'
    while(!iteratorResult.done) {
        // pass to the callback the value of each step
        cb(iteratorResult.value);

        // move forward the iterator
        iteratorResult = iterator.next();
    };

    // before returning, let the iterator do some cleanup
    iterator.return && iterator.return();
}

사용 예를 따르십시오.


iterateOver([1,2,3], console.log); // 1, 2, 3


for-of 루프 


운 좋게도 이러한 유형의 함수를 작성할 필요가 없습니다. JavaScript는 전용 구문 인 for-of 루프를 제공합니다. 

리는 다음과 같이 요약 할 수 있습니다.

  1. iterable의 @@ iterator 메소드를 호출하십시오.
  2. 수신 된 반복자의 다음 메소드를 인수 없이 호출하십시오.
  3. 방금 수신 된 반복자 결과의 완료 플래그가 true로 설정되어 있는지 확인하십시오. 그렇다면 포인트 5로 이동하십시오. 그렇지 않으면 계속하십시오.
  4. 반복자 결과에 포함 된 값을 클라이언트에 제공 한 다음 포인트 2로 이동하십시오.
  5. 반복자 결과를 버리고 메소드가 존재하는 경우 인수 없이 반복자의 리턴 메소드를 호출하십시오.
for(const value of [1,2,3]) {
    console.log(value); // 1, 2, 3
} 


Array destructuring and the array spread operator 


const [a, b] = iterable과 같은 명령어 뒤에; const clone = [... iterable]은 동일한 메커니즘을 사용합니다. 반복자는 iterable에서 일반적으로 배열 파괴와 함께 부분적으로만 사용되지만 array spread 연산자로 빠져 나올 때까지 가져옵니다.


Custom iterators 


마지막으로 위에서 논의한 세 가지 인터페이스의 두 가지 사용자 지정 구현 인 컬렉션의 반복자와 제작자의 반복을 살펴 보겠습니다.


Collection 


다음 단순 콜렉션은 특정 텔레 그램 그룹에 가입 한 사용자의 모든 이름을 저장합니다. 부울 플래그는 사용자가 그룹의 관리자인지 여부를 나타냅니다.


const users = {
    james: false,
    andrew: true,
    alexander: false,
    daisy: false,
    luke: false,
    clare: true,
}


이제 관리자 이름 만 반환하는 이터레이터를 만들어 봅시다.


가장 먼저 할 일은 @@ iterator 메소드를 추가하여 users 컬렉션을 반복 가능하게 만드는 것입니다.


const users = {
    // ...users

    [Symbol.iterator]() {
        // each call will return a new iterator
        const iterator = {
            next() {
                // we set the iterator skeleton
                // adding only an almost empty 'next' method, for now
                return { done: true };
            }
        }

        return iterator;
    }
}


ECMAScript 관점에서 반복자 인터페이스를 올바르게 구현하는 데 필요한 모든 요구 사항을 충족했습니다.


분명히 결과는 우리가 실제로 필요한 것과는 거리가 멀다. 그리고 우리는 위에서 언급 한 규칙을 존중하지도 않습니다. 그러나 알다시피, 사용자는 이제 본격적인 iterable이며 for-of 루프뿐만 아니라 array destructuring 및 array spread 연산자와 함께 안전하게 사용할 수 있습니다.


지금은 충분히 재미있어서 필요한 논리를 추가하겠습니다. 두 번째로 해야 할 일은 객체의 키가 있는 배열을 얻는 것입니다. 인덱스로 반복 할 것입니다. 키 배열과 인덱스는 생성 된 각 반복 자마다 고유해야 하므로 @@ iterator 메소드를 호출하는 동안 작성됩니다.

const users = {
    // ...users

    [Symbol.iterator]() {
        // here the relevant parts
        const keys = Object.keys(this);
        let index = 0;

        const iterator = {
            next() {
                return { done: true };
            }
        }

        return iterator;
    }
}

각 반복 단계에서 키 배열 내에서 하나 이상의 위치를 ​​전진해야 하고 users 컬렉션 내의 실제 부울 플래그와 일치하지 않는 위치는 건너 뜁니다.


다음 메소드 서명을 변경 하여 이 컨텍스트를 유지하기 위해 화살표 함수로 바꿨습니다.


const users = {
    // ...users

    [Symbol.iterator]() {
        const keys = Object.keys(this);
        let index = 0;

        const iterator = {
            // here the relevant parts
            next: () => {
                // this === 'users'
                // skip all the normal users '!this[keys[index]]'
                // stop if we ran out of users 'index < keys.length'
                while (
                    !this[keys[index]] &&
                    index < keys.length
                ) { index++; }


                return { done: true };
            }
        }

        return iterator;
    }
}


!this [keys [index]]에 의해 두려워하지 마십시오. 키는 users 객체의 키가 포함 된 배열이므로 텔레 그램 그룹 사용자 이름이 포함 된 배열입니다. 따라서 keys [index]는 주어진 인덱스에서 사용자 이름 인 키를 반환합니다. 그 후,이 키는 사용자 상태에 따라 true 또는 false를 반환합니다.


이제 편의상 {done : true}로 설정 한 반복자 결과에 초점을 맞추겠습니다. 당신이 묻는다면, 우리는 빈 객체 {}를 반환 할 수 있었고 IteratorResult 인터페이스는 어쨌든 존중되었을 것입니다. 그러나 완료된 플래그 누락은 done : false를 의미한다는 것을 잊지 마십시오. 따라서 for-of 루프 내에서 users 객체를 사용하려고 시도한 경우 for-of가 종료되지 않았기 때문에 브라우저가 중단 된 것입니다.


done 플래그를 올바르게 설정하려면 keys 배열의 length 속성으로 인해 키 수를 초과했는지 확인할 수 있습니다. 각 반환 값을 설정하려면 관리자 이름을 원하기 때문에 현재 색인에 해당하는 이름을 얻을 수 있습니다.


const users = {
    // ...users

    [Symbol.iterator]() {
        const keys = Object.keys(this);
        let index = 0;

        const iterator = {
            next: () => {
                while (!this[keys[index]] && index < keys.length) index++;


                // here the relevant part
                return { 
                    done: index >= keys.length,
                    // after reading the name corresponding to the current index,
                    // do not forget to move forward the 'index'
                    // for the next iteration
                    value: keys[index++],
                };
            }
        }

        return iterator;
    }
}


그게 다야! 

그것을 사용해 보자 :


[...users].forEach(name => console.log(name));
// andrew, clare


A necessary digression 


배열을 보자.


const array = [1, 2, 3];


for-of 루프를 공급하십시오.


for (const n of array) {
    console.log(n);
}

우리는 이미 출력을 알고 있습니다.


// first iteration: 1
// second iteration: 2
// third iteration: 3


어때요?


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

// we no more provide to the 'for-of' loop the array
// but we provide an iterator of the array
for (const n of iterator) {
    console.log(n);
}


당신은 무엇을 기대합니까? 예외? 잘못된! 결과는 정확히 동일합니다.


// first iteration: 1
// second iteration: 2
// third iteration: 3


우리의 커스텀 iterable을 사용해 봅시다 :


const userIterator = users[Symbol.iterator]();

for (const name of userIterator) {
    console.log(name);
}

당신은 무엇을 기대합니까? 이전과 동일한 결과 (앤드류, 클레어를 의미)? 잘못된! 예외가 발생합니다. TypeError : userIterator를 반복 할 수 없습니다.


물론 userIterator는 반복 가능하지 않으며 반복자입니다! 그러나 잠시만 기다려 보자. 왜 배열 이터레이터로 소란스럽지 않니? 차이점이 뭐야? 진실은 배열 반복자와 같은 기본 반복자가 반복자와 반복 가능하다는 것입니다. 그러나 세계에서 반복자를 반복한다는 것은 무엇을 의미합니까?


https://jfet97.github.io/JavaScript-Iterators-and-Generators/ 




페이지 정보

조회 78회 ]  작성일19-10-12 21:38

웹학교