이터레이션 프로토콜 (Iteration protocols)
이터레이션 프로토콜은 내장 객체 또는 구문이 아닌 프로토콜로써 몇가지 규칙에 따라 모든 객체에서 구현될 수 있다.
ES6에서 순회 가능한(Iterable) 자료 구조를 만들기 위해 도입 됐다.
이터레이션 프로토콜에는 이터러블 프로토콜과 이터레이터 프로토콜이 있다.
이터러블 프로토콜 (Iterable protocol)
이터러블은 for ... of
문으로 순회할 수 있으며 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있다.
이터러블은 Symbol.iterator
를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속 받은 Symbol.iterator
메서드를 호출하여 구현할 수 있다.
이터레이터 프로토콜 (Iterator protocol)
이터러블의 Symbol.iterator
메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환한다.
이터레이터는 next 메서드를 가지고 있어 호출하면 이터러블을 순회하고, value와 done 프로퍼티를 갖는 이터레이터 result
객체를 반환한다.
value
는 현재 순회중인 이터러블의 값을 반환done
은 이터러블의 순회 완료 여부를 반환
즉, 이터레이터는 이터러블의 요소를 탐색하기 위한 포인터 역할을 한다.
for ... of
문은 [Symbole.iterator]()
메소드의 호출로 시작해서 이터러블 값을 next()
를 반복적으로 호출하여 순회한다. 순회가 끝나면 done: true
를 반환한다.
[Symbol.iterator]()
// 아래 처럼 next 메소드를 통해 이터러블 요소를 탐색하기 위한 포인터 되어있다.
{
next() {
return { value: any, done: boolean }
}
}
// 배열은 이터러블이다.
const array = [1, 2, 3];
// 이터러블은 Symbol.iterator 메소드를 소유한다.
// Symbol.iterator 메소드는 이터레이터를 반환한다.
let iter = array[Symbol.iterator]();
// 이터레이터는 next 메소드를 소유한다.
// next 메소드는 이터레이터 result 객체를 반환한다.
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: undefined, done: true}
[Symbol.iterator]
Symbol.iterator
를 프로퍼티 key로 사용한 메소드를 가진 객체는 이터러블이다.
const iterable1 = {};
iterable1[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
console.log([...iterable1]); // [1, 2, 3]
.iterator()
가 아닌[Symbol.iterator]
메소드를 사용하는 이유기존에 사용 중인 객체가 이미
.iterator()
메소드를 갖고 있다면 문제가 될 수 있다.따라서 평범한 문자열 대신
Symbol
을 사용하여 고유한 메소드를 정의해준 것이다.
const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function';
// 배열, 문자열, Map, Set 등은 이터러블이다.
isIterable([]); // true
isIterable(''); // true
isIterable(new Map()); // true
isIterable(new Set()); // true
isIterable({}); // false
빌트인 이터러블
자바스크립트는 이터레이션 프로토콜을 준수한 객체인 빌트인 이터러블을 제공한다.
빌트인 이터러블 | Symbol.iterator 메서드 |
---|---|
Array | Array.prototype[Symbol.iterator] |
String | String.prototype[Symbol.iterator] |
Map | Map.prototype[Symbol.iterator] |
Set | Set.prototype[Symbol.iterator] |
TypeArray | TypeArray.prototype[Symbol.iterator] |
arguments | arguments[Symbol.iterator] |
DOM 컬렉션 | NodeList.prototype[Symbol.iterator] HTMLCollection.prototype[Symbol.iterator] |
for ... of 문
for (변수 선언문 of 이터러블) { ... }
이와같이 사용할 수 있다.
// 배열은 이터러블
for (const item of [1, 2, 3]) {
// item 변수에 순차적으로 1, 2, 3이 할당 된다.
console.log(item); // 1 2 3
}
// 위 동작을 for문으로는 다음처럼 표현할 수 있다.
const iterable = [1, 2, 3];
// 이터러블의 Symbol.iterator 메서드를 호출하여 이터레이터를 생성한다.
const iterator = iterable[Symbol.iterator]()
for ( ;; ) {
// next 메서드를 호출하여 이터러블을 순회한다.
const res = iterator.next();
// result 객체의 done 프로퍼티 값이 true이면 이터러블 순회를 중단한다.
if (res.done) break;
const item = res.value;
console.log(item); // 1 2 3
}
이터러블과 유사 배열 객체
유사 배열 객체는 배열처럼 인덱스로 프로퍼티 값에 접근 가능하고 length 프로퍼티를 갖는 객체를 말한다.
유사 배열 객체도 length 프로퍼티를 갖기 때문에 for 문으로 순회할 수 있고, 인덱스를 나타내는 숫자 형식의 문자열 프로퍼티를 키로 가지고 있기 때문에 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있다.
// 유사 배열 객체
const arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
}
// 유사 배열 객체는 length 프로퍼티를 갖기 때문에 for문으로 순회할 수 있다.
for (let i = 0; i < arrayLike.length; i++) {
console.log(arrayLike[i]); // 1 2 3
}
하지만 유사 배열 객체는 이터러블이 아는 일반 객체다.
// Symbol.iterator 메서드가 없기 때문에 for ... of 문으로 순회할 수 없다.
for (const item of arrayLike) {
console.log(item); // 1 2 3
} // TypeError: arrayLike is not iterable
하지만 arguments, NodeList, HTMLCollection은 유사 배열 객체이면서 이터러블이다.
이유는 ES6에서 이터러블이 도입되면서 해당 객체에 Symbol.iterator
메서드를 구현했기 때문에 이터러블이 되었다.
// 유사 배열 객체
const arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
}
// Array.from을 통해 유사 배열 객체를 배열로 변환할 수 있다.
const arr = Array.from(arrayLike);
console.log(arr); // [1, 2, 3]
for (const item of arr) {
console.log(item); // 1 2 3
}
사용자 정의 이터러블
일반 객체에 이터레이션 프로토콜을 준수하도록 구현하면 사용자 정의 이터러블이 된다.
// 피보나치 수열을 구현한 사용자 정의 이터러블
const fibonacci = {
// Symbol.iterator 메서드를 구현하여 이터러블 프로토콜 준수
[Symbol.iterator]() {
let [pre, cur] = [0, 1];
const max = 10;
return {
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur, done: cur >= max }
}
}
}
}
for (const num of fibonacci) {
console.log(num); // 1 2 3 5 8
}
const arr = [...fibonacci];
console.log(arr); // [1, 2, 3, 5, 8]
const [first, second, ...rest] = fibonacci;
console.log(first, second, rest); // 1 2 [3, 5, 8]
만들어진 사용자 정의 이터러블을 for ... of 문으로 순회할 때마다 next 메서드가 호출되고, done 프로퍼티가 true 값을 가질 때 까지 반복한다.
이처럼 이터러블은 for ... of 문뿐만 아니라 스프레드 문법, 배열 디스트럭처링 할당에도 사용 가능하다.
max는 10으로 고정되어 있는데 이를 인수로 전달받아 이터러블을 반환하는 함수로 만들 수 있다.
// 피보나치 수열을 구현한 사용자 정의 이터러블
const fibonacci = function (max) {
let [pre, cur] = [0, 1];
// Symbol.iterator 메서드를 구현하여 이터러블 프로토콜 준수
return {
[Symbol.iterator]() {
return {
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur, done: cur >= max }
}
}
}
}
}
for (const num of fibonacci(10)) {
console.log(num); // 1 2 3 5 8
}