이터레이션 프로토콜은 ES6에 도입되었으며, 순회 가능한(iterable) 데이터 컬렉션을 만들기 위해 ECMAScript 사양에 정의하여 미리 약속한 규칙입니다.
ES6 이전의 순회 가능한 데이터 컬렉션(배열, 문자열, 유사 배열 객체, DOM 컬렉션) 등은 통일된 규약 없이 각자 나름의 구조를 가지고 for, for...in, forEach
등을 사용하여 다양한 방법으로 순회 가능하였습니다.
ES6에서는 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일하여 for...of
, 스프레드 문법, 배열 구조 분해 할당의 대상으로 사용할 수 있도록 표준화 하였습니다.
이터레이션 프로토콜에는 이터러블 프로토콜과 이터레이터 프로토콜이 있습니다.
Well-known Symbol인
Symbol.iterator
를 키로 갖는 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속한Symbol.iterator
메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환하는 프로토콜.
이터러블 포로토콜을 준수한 객체를 이터러블이라고 합니다.
이터레이터는
next
메서드가 정의되어 있으며,next
메서드를 호출하면 이터러블을 순회하며 value와 done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환하는 프로토콜
이터러블 프로토콜은 준수한 객체를 이터러블이라고 합니다.
이터러블은 for...of
, 스프레드 문법, 배열 구조분해 할당의 대상으로 사용할 수 있습니다.
이터러블인지 확인하기 위해서는 다음과 같이 할 수 있습니다.
console.log(typeof obj[Symbol.iterator] === 'function');
대표적인 표준 빌트인 이터러블은 다음과 같습니다.
Array.prototype[Symbol.iterator]
String.prototype[Symbol.iterator]
Map.prototype[Symbol.iterator]
Set.prototype[Symbol.iterator]
arguments[Symbol.iterator]
NodeList.prototype[Symbol.iterator], HTMLCollection.prototype[Symbol.iterator]
일반 객체는 Symbol.iterator
를 키로 갖는 메서드를 따로 정의하지 않는 이상 Symbol.iterator
메서드를 프로토타입 체인으로 상속할 수 없기 때문에, 이터러블이 될 수 없습니다.
🚨일반 객체는 이터러블은 아니지만 스프레드 문법은 사용할 수 있습니다.
일반 객체라도 이터러블 프로토콜을 준수하도록 구현하면 이터러블이 될 수 있습니다.
이터레이터는 next
메서드를 가져야합니다.
next
는 이터러블의 각 요소를 순회하기 위한 포인터 역할을 합니다.
next
메서드를 호출하면 이터러블을 순차적으로 한 단계식 순회하면서 순회 결과를 나타내는 이터레이터 리절트 객체를 반환합니다.
이터레이터 리절트 객체는 value
, done
프로퍼티를 가집니다.
value
는 현재 순회 중인 이터러블의 요소 값이며, done
프로퍼티는 이터러블의 순회 완료 여부를 나타냅니다.
for...of
반복문이 시작되면, Symbol.iterator
메서드를 호출합니다.(그런 메서드가 없다면 에러가 발생합니다.)
이후 for...of
는 Symbol.iterator
메서드를 호출해 반환된 이터레이터만을 대상으로 동작합니다.
for...of
에서 다음 값이 필요하면 for...of
는 next
메서드를 호출합니다.
next
메서드의 반환값은 이터레이터 리절트 객체이므로 {done: Boolean, value: any}
와 같은 형태여야 합니다.
done=false
일 때는 value
에는 현재 순회중인 이터러블의 요소가 저장됩니다.
이터레이터 리절트 객체의 value 프로퍼티 값을 for...of
문의 변수에 할당합니다.
done=true
는 반복이 종료되었음의 의미하고, 이터러블 순회를 중단합니다.
for...in
문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입 프로퍼티 중에서 프로퍼티 애트리뷰트(플래그) [[Enumerable]]
값이 true인 프로퍼티를 순회하며 열거합니다.
심볼 포스팅에서 이야기 했듯이 심볼 키는 순회 대상에서 제외됩니다.
유사 배열 객체는 배열 처럼 인덱스로 프로퍼티 값에 접근하고, length 프로퍼티를 갖는 객체입니다.
유사 배열 객체는 length 프로퍼티를 가지기 때문에 for
문으로 순회가 가능하고, 인덱스로 사용되는 숫자 형식의 문자열을 프로퍼티 키로 가지기 때문에 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있습니다.
하지만 Symbol.iterator
메서드가 존재하지 않기 때문에 이터러블이 아닙니다.
따라서 for...of
로 순회할 수 없습니다.
arguments, NodeList, HTMLCollection
은 유사 배열 객체이면서 이터러블입니다.
원래는 유사 배열 객체였는데 ES6가 도입되면서 arguments, NodeList, HTMLCollection
에 Symbol.iterator
메서드를 구현해 이터러블이 되었습니다.
Array.from
메서드는 유사 배열 객체나 이터러블을 인수로 받아 Array
로 만들어줍니다.
Array
는 대표적인 빌트인 이터러블이기 때문에 배열로 변환해서 for...of
문의 대상으로 사용할 수 있습니다.
let range = {
from: 1,
to: 5
};
// 1. for..of 최초 호출 시, Symbol.iterator가 호출됩니다.
range[Symbol.iterator] = function() {
// Symbol.iterator는 이터레이터 객체를 반환합니다.
// 2. 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, 이때 다음 값도 정해집니다.
return {
current: this.from,
last: this.to,
// 3. for..of 반복문에 의해 반복마다 next()가 호출됩니다.
next() {
// 4. next()는 값을 이터레이터 리절트 객체 {done:.., value :...}형태로 반환해야 합니다.
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
range
자체를 이터레이터로 만들면 코드가 더 간단해집니다.
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
이터러블 프로토콜은 Symbol.iterator
메서드를 호출하면 이터레이터 프로토콜을 준수하는 이터레이터를 반환하는 프로토콜입니다.
이터레이터 프로토콜은 next
메서드가 정의되어 있으며, next
메서드를 호출하면 이터레이터 리절트 객체(value와 done 프로퍼티를 가짐)를 반환하는 프로토콜입니다.
range
는 두 프로토콜을 모두 지켰습니다.
Symbol.iterator
가 range
자체를 반환합니다.
range
는 Symbol.iterator
메서드를 소유하고 있으며 동시에 next
메서드를 소유한 객체입니다.
또한 next
메서드에서는 이터레이터 리절트 객체를 반환하고 있기 때문에 이터레이터 프로토콜도 지켰습니다.
따라서 range
객체는 이터러블이면서 이터레이터인 객체입니다.