Iteration protocols

ECMAScript 2015 (ES6)에는 새로운 문법이나 built-in 뿐만이 아니라, protocols(표현법들)도 추가되었습니다. 이 protocol 은 일정 규칙만 충족한다면 어떠한 객체에 의해서도 구현될 수 있습니다.

2개의 protocol이 있습니다 : iterable protocol 과 iterator protocol.

The iterable protocol

iterable protocol 은 JavaScript 객체들이, 예를 들어 for..of 구조에서 어떠한 value 들이 loop 되는 것과 같은 iteration 동작을 정의하거나 사용자 정의하는 것을 허용합니다. 다른 type 들(Object 와 같은)이 그렇지 않은 반면에, 어떤 built-in type 들은 Array 또는 Map 과 같은 default iteration 동작으로 built-in iterables 입니다.

iterable 하기 위해서 object는 @@iterator 메소드를 구현해야 합니다. 이것은 object (또는 prototype chain 의 오브젝트 중 하나) 가 Symbol.iterator key 의 속성을 가져야 한다는 것을 의미합니다 :

Property Value
[Symbol.iterator] object를 반환하는, arguments 없는 function. iterator protocol 을 따른다.

어떠한 객체가 반복(Iterate)되어야 한다면 이 객체의 @@iterator 메소드가 인수없이 호출되고, 반환된 iterator는 반복을 통해서 획득할 값들을 얻을 때 사용됩니다.

The iterator protocol

iterator protocol 은 value( finite 또는 infinite) 들의 sequence 를 만드는 표준 방법을 정의합니다. 

객체가 next() 메소드를 가지고 있고, 아래의 규칙에 따라 구현되었다면 그 객체는 iterator이다:

Property Value
next

아래 2개의 속성들을 가진 object 를 반환하는 arguments 없는 함수 :

  • done (boolean)
    • Iterator(반복자)가 마지막 반복 작업을 마쳤을 경우 true. 만약 iterator(반복자)에 return 값이 있다면 value의 값으로 지정된다. 반환 값에 대한 설명은 여기.
    • Iterator(반복자)의 작업이 남아있을 경우 false. Iterator(반복자)에 done 프로퍼티 자체를 특정짓지 않은 것과 동일하다.
  • value - Iterator(반복자)으로부터 반환되는 모든 자바스크립트 값이며 done이 true일 경우 생략될 수 있다.

몇몇 iterator들은 iterable(반복 가능)이다:

var someArray = [1, 5, 7];
var someArrayEntries = someArray.entries();

someArrayEntries.toString();           // "[object Array Iterator]"
someArrayEntries === someArrayEntries[Symbol.iterator]();    // true

 Iteration protocols 사용 예시

String 은 built-in iterable 객체의 한 예시입니다.

var someString = "hi";
typeof someString[Symbol.iterator];          // "function"

String 의 기본 iterator 는 string 의 문자를 하나씩 반환합니다.

var iterator = someString[Symbol.iterator]();
iterator + "";                               // "[object String Iterator]"

iterator.next();                             // { value: "h", done: false }
iterator.next();                             // { value: "i", done: false }
iterator.next();                             // { value: undefined, done: true }

spread operator와 같은 특정 내장 구조(built-in constructs)들은 실제로는 동일한 iteration protocol을 사용한다:

[...someString]                              // ["h", "i"]

사용자만의 @@iterator를 특정함으로써 원하는 반복 행위(iteration behavior)를 설정할 수 있다:

var someString = new String("hi");          // need to construct a String object explicitly to avoid auto-boxing

someString[Symbol.iterator] = function() {
  return { // this is the iterator object, returning a single element, the string "bye"
    next: function() {
      if (this._first) {
        this._first = false;
        return { value: "bye", done: false };
      } else {
        return { done: true };
      }
    },
    _first: true
  };
};

재설정된 @@iterator가 어떻게 내장 구조(built-in constructs)의 반복 행위에 영향을 주는지 참고:

[...someString];                              // ["bye"]
someString + "";                              // "hi"

Iterable 예시

내장 iterables

String, Array, TypedArray, Map and Set 는 모두 내장 iterable이다. 이 객체들의 프로토타입 객체들은 모두 @@iterator 메소드를 가지고 있기 때문이다.

사용자 정의된 iterables

이렇게 고유한 iterables 를 만들 수 있다.

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable]; // [1, 2, 3]

Iterable을 허용하는 내장 API들

Iterable을 허용하는 많은 내장 API들이 있다. 예를 들어: Map([iterable]), WeakMap([iterable]), Set([iterable]) and WeakSet([iterable])이 그것이다:

var myObj = {};
new Map([[1,"a"],[2,"b"],[3,"c"]]).get(2);               // "b"
new WeakMap([[{},"a"],[myObj,"b"],[{},"c"]]).get(myObj); // "b"
new Set([1, 2, 3]).has(3);                               // true
new Set("123").has("2");                                 // true
new WeakSet(function*() {
    yield {};
    yield myObj;
    yield {};
}()).has(myObj);                                         // true

뿐만 아니라 Promise.all(iterable), Promise.race(iterable)와 Array.from() 또한 해당된다.

Iterable과 함께 사용되는 문법

for-of loops, spread operator, yield* destructuring assignment는 iterable과 함께 사용되는 구문(statements)과 표현(expressions)이다.

for(let value of ["a", "b", "c"]){
    console.log(value);
}
// "a"
// "b"
// "c"

[..."abc"]; // ["a", "b", "c"]

function* gen(){
  yield* ["a", "b", "c"];
}

gen().next(); // { value:"a", done:false }

[a, b, c] = new Set(["a", "b", "c"]);
a // "a"

잘 정의되지 못한 iterables

만약 Iterable의 @@iterator 메소드가 iterator 객체를 반환하지 않는다면 그것은 잘 정의되지 못한 iterable이라고 할 수 있다. 이러한 iterable을 사용하는 것은 런타임 예외나 예상치 못한 결과를 불러올 수 있다:

var nonWellFormedIterable = {}
nonWellFormedIterable[Symbol.iterator] = () => 1
[...nonWellFormedIterable] // TypeError: [] is not a function

Iterator 예시

간단한 iterator

function makeIterator(array){
    var nextIndex = 0;

    return {
       next: function(){
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    };
}

var it = makeIterator(['yo', 'ya']);

console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done);  // true

무한 iterator

function idMaker(){
    var index = 0;

    return {
       next: function(){
           return {value: index++, done: false};
       }
    };
}

var it = idMaker();

console.log(it.next().value); // '0'
console.log(it.next().value); // '1'
console.log(it.next().value); // '2'
// ...

Generator와 함께 사용된 iterator

function* makeSimpleGenerator(array){
    var nextIndex = 0;

    while(nextIndex < array.length){
        yield array[nextIndex++];
    }
}

var gen = makeSimpleGenerator(['yo', 'ya']);

console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done);  // true



function* idMaker(){
    var index = 0;
    while(true)
        yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // '0'
console.log(gen.next().value); // '1'
console.log(gen.next().value); // '2'
// ...

generator object 는 iterator 또는 iterable 인가?

generator object 는 iterator 이면서 iterable 입니다.

var aGeneratorObject = function*(){
    yield 1;
    yield 2;
    yield 3;
}();
typeof aGeneratorObject.next;
// "function", 이것은 next 메서드를 가지고 있기 때문에 iterator입니다.
typeof aGeneratorObject[Symbol.iterator];
// "function", 이것은 @@iterator 메서드를 가지고 있기 때문에 iterable입니다.
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// true, 이 Object의 @@iterator 메서드는 자기자신(iterator)을 리턴하는 것으로 보아 잘 정의된 iterable이라고 할 수 있습니다.
[...aGeneratorObject];
// [1, 2, 3]

See also