반복 가능한(iterable, 이터러블) 객체는 배열을 일반화한 객체입니다. 이터러블 이라는 개념을 사용하면 어떤 객체에든 for..of 반복문을 적용할 수 있습니다.
배열을 일반화한 객체?
배열과 비슷한 형태와 기능을 가지면서도 내부 구현이 다른 객체.
= 반복 가능한 객체(iterable objects)
→ Array, Map, Set 등은 물론이며 String도 반복 가능
반대로 이터러블이 아닌 객체는 for...of을 적용할 수 없다.
하지만 이러한 객체가 어떤 것들의 컬렉션(목록, 집합 등)을 나타내고 있는 경우, for..of 반복문을 사용할 수 있다면 컬렉션을 순회하는데 유용할 것이다.
for..of를 적용하기에 적합해 보이는 배열이 아닌 객체를 만들고 이 객체를 이터러블로 만들어보자!
예시의 객체 range는 숫자 간격을 나타낸다.
let range = {
from: 1,
to: 5
};
// 아래와 같이 for..of가 동작할 수 있도록 하는 게 목표입니다.
// for(let num of range) ... num=1,2,3,4,5
Symbol.iterator(특수 내장 심볼)이라는 메서드를 객체에 추가하면 배열이 아닌 객체에 for...of을 사용할 수 있게 된다.
for..of가 시작되자마자 for..of는 Symbol.iterator를 호출한다.(Symbol.iterator가 없으면 에러)
Symbol.iterator는 반드시 이터레이터(interator)를 반환해야 한다.
1. 이터레이터
: 이터러블 객체를 탐색하는 방법을 정의한 객체. 메서드 next()를 가진다.2. next()
: 호출 시 이터레이터 내부의 다음 값에 대한 두 프로퍼티 value, done을 반환한다.
{done: Boolean, value: any}의 형태3. value
: 이터레이터에서 반환된 값4. done
: 이터레이터 내부의 모든 값을 반환했는지를 나타내는 boolean 값
done=true는 반복이 종료되었음을 의미한다.
done=false일땐 value에 다음 값이 저장된다.
이후 for..of는 반환된 객체(이터레이터)만을 대상으로 동작한다.
for..of에 다음 값이 필요하면, for..of는 이터레이터의 next()를 호출한다.
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
}
이터러블 객체의 핵심은 '관심사의 분리(Separation of concern, SoC)'에 있다.
- range엔 메서드 next()가 없다.
- 대신 rangeSymbol.iterator를 호출해서 만든 ‘이터레이터’ 객체와 이 객체의 메서드 next()에서 반복에 사용될 값을 만들어낸다.
이렇게 하면 이터레이터 객체와 반복 대상인 객체(range)를 분리할 수 있다.
배열과 문자열은 가장 광범위하게 쓰이는 내장 이터러블이다.
for..of는 문자열의 각 글자를 순회합니다.
for (let char of "test") {
// 글자 하나당 한 번 실행됩니다(4회 호출).
alert( char ); // t, e, s, t가 차례대로 출력됨
}
서로 게이트 쌍(surrogate pair)에도 잘 동작한다.
let str = '𝒳😂';
for (let char of str) {
alert( char ); // 𝒳와 😂가 차례대로 출력됨
}
for..of 없이 이터레이터를 직접 호출하여 문자열 순회하기
문자열 이터레이터를 만들고, 여기서 값을 ‘수동으로’ 가져오는 예시이다.
let str = "Hello";
// for..of를 사용한 것과 동일한 작업을 합니다.
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;//이터레이터가 모든 값을 순회했으므로 > break
alert(result.value); // 글자가 하나씩 출력됩니다.
}
이터레이터를 명시적으로 호출하는 경우는 거의 없는데, 이 방법을 사용하면 for..of를 사용하는 것보다 반복 과정을 더 잘 통제할 수 있다는 장점이 있다.
반복을 시작했다가 잠시 멈춰 다른 작업을 하다가 다시 반복을 시작하는 것과 같이 반복 과정을 여러 개로 쪼개는 것도 가능하다.