이전 Symbol
에서 iterator
에 대해서 가볍게 살펴보았다.
전에 살펴보았던 iterator
에 대한 내용을 조금 더 깊게 공부해보자
ES6
에서 도입된 이터레이션 프로토콜은 순회 가능한 (iterable
) 자료구조를 만들기 위해 정의한 미리 약속된 규칙이다.
순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일하여 for...of
, 스프레드 문법
, 배열 디스트럭처링
할당의 대상으로 사용 할 수 있도록 일원화 하였다.
이터레이션 프로토콜
Well-Known-Symbol
인 Symbol.iterator
를 프로퍼티 키로 사용한 메소드를 직접 구현하거나, 프로토타입 체인 상속을 통해 Symbol.iterator
메소드를 호출하여 이터레이터 프로토콜을 준수한 이터레이터를 반환한다. 정리
이터레이션 프로토콜은 이터러블 프로토콜을 준수하는 이터레이터를 반환하는 프로토콜이다.
이터러블 프로토콜
next
메소드를 소유하며 next
메소드를 호출하면 result , done
프로퍼티를 소유한 객체를 반환한다. 이러한 규약을 이터러블 프로토콜
이라고 하며, 해당 프로토콜을 준수하는 객체를 이터레이터
라고 한다. 정리
이터레이션 프로토콜
은 다양한 자료구조에서 동일하게for of
문이나스프레드 문법, 디스트럭쳐링
을 하기 위해 규약된 프로토콜로 ,이터러블 프로토콜
을 준수하는이터레이터
를 반환 할 수 있도록 한다.
이터레이션 프로토콜
을 지키는 객체들을 순회 가능한 자료구조라고 한다.
const isIterable = (dataStructure) =>
dataStructure && typeof dataStructure[Symbol.iterator] === 'function';
console.log(isIterable([])); // true
console.log(isIterable(' ')); // true
console.log(isIterable(new Map())); // true
console.log(isIterable(new Set())); // true
console.log(isIterable({})); // false
이터러블한 자료구조는 null
값이 아니면서 Symbol.iterator
메소드를 프로퍼티로 갖는 자료구조를 말한다.
Symbol.iterator
를 갖는 자료구조는 for of
문이나 스프레드 문법, 디스트럭쳐링을 사용 할 수 있다.
const arr = [1, 2, 3];
for (const num of arr) {
console.log(num);
} // 1 2 3 (for of 문)
const [a, ...rest] = arr; // 스프레드 문법과 디스트럭쳐링
console.log(a); // 1
console.log(rest); // [ 2 , 3]
Symbol.iterator
메소드를 가지지 않는 자료구조는 위에서 스프레드 문법을 제외하고 말한 것을 사용 할 수 없다.
const obj = { a: 1, b: 2 };
console.log(Symbol.iterator in obj); // false
for (const property of obj) {
console.log(obj[property]);
} // TypeError: obj is not iterable
const obj = { a: 1, b: 2 };
const spreadObj = { ...obj };
console.log(spreadObj); // { a: 1, b: 2 }
스프레드 문법은 얕은 복사를 허용한다.
정리
이터러블한 객체는 내부 메소드로
Symbol.iterator
를 가지고 있다.
이터레이터는 이터레이션 프로토콜
을 준수하는 자료구조에서 Symbol.iterator
메소드를 호출하여 반환된 객체를 의미한다고 하였다.
그럼 이터레이터
객체는 어떻게 생겼을까 ?
0부터 3까지 순회하는 iterator
를 만들어보자
const iterator = {
cur: 0,
max: 3,
next() {
return {
value: this.cur < this.max + 1 ? this.cur++ : undefined,
done: this.cur > this.max + 1,
};
},
};
console.log(iterator.next()); // { value: 0, done: false }
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
간단하게 설명하면 iterator
는 현재 값과 최대값을 지정한 후 next()
메소드를 호출하면 value , done
이 담긴 객체를 반환한다.
이 때 value
에는 현재값이 담긴다. (담긴 이후에는 iterator
객체의 현재 값을 1 증가 시킨다.)
그리도 done
은 boolean
타입의 값으로 현재 값이 최대값보다 작거나 같을 때 까지 true
를 반환한다.
그러면 for of
로 iterator
를 순회하는 과정은 다음과 같다.
const iterator = {
cur: 0,
max: 3,
next() {
return {
value: this.cur++,
done: this.cur > this.max + 1,
};
},
};
for (;;) {
const res = iterator.next();
if (res.done) break; // done 이 true 면 종료
console.log(res.value); // 1 2 3
}
while
문으로 구현해도 된다.
done
값이 조건에 만족하면 멈추고, 그게 아닐 시엔 현재 값을 return
한다.
알고리즘 공부를 하다보면 만나는 피보나치 수열을 이터레이션 객체를 통해 생성해보자
이터레이션 프로토콜을 준수하는 객체는 두 가지 조건을 만족해야 한다.
Symbol.iterator
메소드를 가지고 있어야 한다.Symbol.iterator
는 이터러블 프로토콜을 준수하는 객체를 반환해야 한다.const iterable = {
[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 iterable) {
console.log(num); // 1 2 3 5 8
}
슈류륙
iterable
객체는 메소드 [Symbol.iterator]
만 가지고 있다.
이 Symbol.iterator
메소드는 호출되면 지역 변수로 pre , cur , max
를 가지고 있고
next
라는 메소드가 담긴 객체를 반환한다.
이 때 next
메소드는 iterable
내의 지역 객체들을 참조하는 클로저
이다.
위에서 봤단 것처럼 for of
문을 돌며 iterable
의 클로저 함수인 next()
를 지속적으로 호출하며
const num
에 담긴 값은 반환되는 {value , done}
객체의 value
값이다.
위에서는 최대값인 MAX
를 객체를 생성 할 때 마다 지정해줬어야 했다.
인수로는 못받을까 ?
됩니당
const fibonacciFunc = function fibo(max) {
let [pre, cur] = [0, 1];
return {
[Symbol.iterator]() {
return {
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur, done: cur > max };
},
};
},
};
};
for (const num of fibonacciFunc(20)) {
console.log(num); // 1 2 3 5 8 13
}
위 이터레이션 객체를 반환하는 함수를 만들어 하는 것도 가능하다.
위는 이터레이블 객체를 반환하는 이터레이션 객체를 반환하는 함수를 만들었다.
이 것이 비효율적인 것처럼 느껴진다면
이터레이션 객체이면서 이터레이블 객체인 것을 만드는 것이 가능하다.
const fibonacciFunc = function fibo(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 fibonacciFunc(20)) {
console.log(num); // 1 2 3 5 8 13
}
Symbol.iterator
객체는 자기 자신을 반환하도록 하는 것이다.
불필요한 두 단계를 한 단계로 통합 할 수 있다.
이번엔 위에서 max
값을 지정해주었는데 지정해주지 않고,
max
를 넘는지 안넘는지 평가한 후 반환값을 생성해보자
const fibonacciFunc = function fibo() {
let [pre, cur] = [0, 1];
return {
[Symbol.iterator]() {
return this;
},
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur };
},
};
};
const MAX = 30;
for (const num of fibonacciFunc()) {
if (num > MAX) break;
console.log(num); // 1 2 3 5 8 13 21
}
지연 평가는 데이터를 미리 생성하지 않고 있닥 평가 이후 데이터를 생성하여 메모리를 효율적으로 사용하는 방법을 의미한다.
책에서는 예제를 다음처럼 표현했으나 나는 이전의 방법도 지연평가라고 생각한다.
포인트는 이터레이블 객체는 무한적으로 순회 할 수 있도록 한 후, 평가 결과에 따라 이터레이블을 멈춘다는 것이다.