❓이터러블이란
ES6에서 도입된 이터레이션 프로토콜은 순회 가능한 자료구조를 만들기 위해 ECMAScript 사양에 정의하여 미리 약속한 규칙이다. ES6 이전에는 순회 가능한 자료구조인 배열, 문자열, 유사 배열 객체, DOM 컬렉션 등의 순회는 for문, forEach, for in 문 등이 담당했다. 하지만 ES6에서는 순회 가능한 자료구조를 이터레이션 프로토콜을 준수하는 이터러블로 통일하여 for of 문, 스프레드 문법, 배열 디스트럭처링 할당을 사용할 수 있도록 했다.
1️⃣ 이터러블 프로토콜, 이터레이터 프로토콜
이터러블 프로토콜을 준수하는 객체를 이터러블 이라하고, 이터레이터 프로토콜을 준수하는 객체를 이터레이터라고 한다. 그렇다면 각각의 프로토콜은 무엇을 의미할까?
동일하게 ES6에서 추가된 새로운 데이터 타입인 심벌 중 자바스크립트가 기본으로 제공하는 빌트인 심벌 값을 Well-known Symbol이라고 하는데, 배열, 문자열, Map, Set, TypedArray, arguments, NodeList, HTMLCollection은 Well-known Symbol인 Symbol.iterator를 키로 갖는 메서드를 갖는다. 따라서 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환하게 되는데, 이러한 규약을 이터러블 프로토콜이라고 한다.
위에서 설명한 것처럼 Symbol.iterator 메서드를 호출했을 때 이터레이터가 반환되고, 해당 이터레이터는 next 메서드를 가진다. next 메서드 호출을 통해 이터러블을 순회하게되고 value와 done이라는 프로퍼티를 갖는 이터레이터 리절트 객체를 반환하게 되는데, 이러한 규약을 이터레이터 프로토콜이라고 한다.
const arr = [1, 2, 3, 4, 5];
const obj = { a: 1, b: 2 };
console.log(Symbol.iterator in arr); // true
console.log(Symbol.iterator in obj); // false
배열은 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 상속받는 이터러블이고, 일반 객체는 이터러블 프로토콜을 준수하는 이터러블이 아니다.
2️⃣ 자바스크립트 빌트인 이터러블
자바스크립트에서는 이터레이션 프로토콜을 준수한 객체인 빌트인 이터러블을 제공한다.
빌트인 이터러블 | Symbol.iterator 메서드 |
Array | Array.prototype[Symbol.iterator] |
String | String.prototype[Symbol.iterator] |
Map | Map.prototype[Symbol.iterator] |
Set | Set.prototype[Symbol.iterator] |
TypedArray | TypedArray.prototype[Symbol.iterator] |
arguments | arguments[Symbol.iterator] |
DOM 컬렉션 | NodeList.prototype[Symbol.iterator] HTMLCollection.prototype[Symbol.iterator] |
일반 객체는 이터레이션 프로토콜을 준수하지 않지만 이터레이션 프로토콜을 준수하도록 구현한다면 이터러블이 될 수 있다.
3️⃣ for of 문
for of 문과 형식이 비슷한 for in 문은 객체의 모든 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하는 반면 for of 문은 내부적으로 이터레이터의 next 메서드를 호출하여 이터러블을 순회하고, next 메서드를 통해 반환된 이터레이터 리절트 객체의 value 프로퍼티 값을 for of 문 변수에 할당한다. 이후 이터레이터 리절트 객체의 done 프로퍼티 값이 true이면 이터러블의 순회를 중단한다.
const arr = [1,2,3,4,5];
for(const item of arr) {
console.log(item); // 1 2 3 4 5
}
위의 for of 문 코드의 내부동작은 다음과 같다.
const arr = [1,2,3,4,5]; // 배열은 이터레이션 프로토콜을 준수하는 빌트인 이터러블
const iterator = arr[Symbol.iterator](); // 이터레이터 반환
for(;;) {
const result = iterator.next() // 이터레이터 리절트 객체 반환
if(result.done) break; // done의 값이 true이면 순회 중단
console.log(result.value);
}
4️⃣ 사용자 정의 이터러블
위에서 언급했듯이 일반 객체는 이터레이션 프로토콜을 준수하지 않지만, 이터레이션 프로토콜을 준수하도록 구현한다면 이터러블이 될 수 있다.
다음 코드를 보자.
const fibonacci = {
[Symbol.iterator]() {
let [pre, cur] = [0, 1];
const max = 10;
return {
next() {
[pre, cur] = [cur, pre + cur];
return {
value: cur,
done: cur >= max,
};
},
};
},
};
const iter = fibonacci[Symbol.iterator]();
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: 5, done: false }
console.log(iter.next()); // { value: 8, done: false }
console.log(iter.next()); // { value: 13, done: true }
for (const num of fibonacci) {
console.log(num); // 1 2 3 5 8
}
위의 일반 객체를 이터레이션 프로토콜을 준수하도록 Symbol.iterator 메서드를 구현하고, Symbol.iterator 메서드가 next 메서드를 가지는 이터레이터를 반환하도록 한다. 그리고, 해당 next 메서드는 이터레이터 리절트 객체를 반환한다. 따라서 위의 객체는 이터러블이고, for of 문을 사용할 수 있다. 결과적으로 done 프로퍼티가 true가 되면 반복을 중지한다.
이터러블이면서 이터레이터인 객체를 생성하는 함수
이터러블이면서 이터레이터인 객체를 생성한다면 Symbol.iterator 메서드를 호출하지 않아도 된다.
const fibonacci = function (max) {
let [pre, cur] = [0, 1];
return {
[Symbol.iterator]() {
return this;
},
next() {
[pre, cur] = [cur, pre + cur];
return {
value: cur,
done: cur >= max,
};
},
};
};
for (const num of fibonacci(20)) {
console.log(num); // 1 2 3 5 8 13
}
위의 함수는 Symbol.iterator 메서드와 next 메서드를 모두 소유한 이터러블이면서 이터레이터다. Symbol.iterator 에서 this를 반환하기 때문에 next 메서드를 갖는 이터레이터를 반환한다.