Symbol 값과 이터러블 1편에서 배열은 이터러블이기 때문에 이터러블의 속성을 통한 for ...of
의 동작 원리를 알 수 있었다.
const arr = [1, 2, 3]
let iterator = arr[Symbol.iterator]();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: undefined, done: true }
for (const a of arr) {
console.log(a); // 1 2 3
}
그럼 해당 이터러블을 직접 구현해보자.
다시 한번 정의를 살펴보면 다음과 같다.
이터러블은 이터레이터를 리턴하는 [Symbol.iterator]()
를 가진 값이다.
이터레이터는 { value, done }
을 리턴하는 next()
를 가진 값이다.
이터러블/이터레이터 프로토콜은 이터러블을 for ...of
, 전개 연산자 등과 함께 동작하도록 한 규약을 뜻한다.
위의 정의까지 코드로 구현하면 다음과 같다.
const iterator = {
[Symbol.iterator]() {
let i = 3;
return {
next() {
return i === 0 : { done: true} : { value: i--, done: false}
}
}
}
}
Symbol.iterator()
를 통해 리턴된 이터레이터 또한 Symbol.iterator()
값을 가지고 있다.
즉, 이터러블이면서 이터레이터인 객체를 생성한다.
const arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();
iterator[Symbol.iterator] // ƒ [Symbol.iterator]() { [native code] }
또한 해당 이터레이터 값을 실행한 값은 자기 자신을 뜻한다.
console.log(iterator[Symbol.iterator]() === iterator) // true
이를 well-formed iterable 이라고 한다.
이러한 well-formed iterable 를 사용자 정의 이터러블 코드에 적용시킨다면 다음과 같다.
const iterator = {
[Symbol.iterator]() {
let i = 3;
return {
[Symbol.iterator]() { return this; },
next() {
return i === 0 : { done: true} : { value: i--, done: false}
},
}
}
}
well-formed-iterator의 개념적 정의는 위와 같은데, 이를 구현하는 이유에 대해서 유인동님께서는 이렇게 답변해주셨다.
이터레이터여도 for ...of 나 전개연산자 등으로 남은 값을 소비할 수 있게 하기 위해서입니다. :)