Literator란 무엇인가?
우선 iterator 를 생성하는 방법을 Symbol.iterator 를 활용하여 이터레이터를 지정할 수 있습니다. 이터레이터도 자바스크립트 내에서는 하나의 객체일 뿐입니다. 하지만 특정 메서드를 반드시 포함하고 있는 특이한 객체일 뿐입니다.
이터레이터는 필수 메서드인
next()
메서드를 생성합니다. 이 메서드는 이터레이터를 실행 시킨 후의 결과 객체를 반환해야 합니다. 이러한 조건은 이터레이터 프로토콜에 포함되고 반드시 지켜야 하는 조건에 해당합니다.결론으로 이터레이터는 이터레이터 프로토콜을 준수하는 객체를 이터레이터라고 합니다. 여기서 이터레이터 프로토콜은 next 메서드를 소유하며 next 메서드를 호출할 경우 이터러블을 순회하며 value 와 done 프로퍼티를 갖는 이터레이터 객체를 반환해야 한다는 규칙을 갖고 있고 이러한 규칙을 지키고 있는 객체를 이터레이터라고 정의할 수 있습니다. 이에 대한 예시를 제공해보겠습니다.
function sync(iterable, depth) {
const iterator = iterable[Symbol.iterator]();
const iteratorStack = [iterator];
return {
[Symbol.iterator]() {
return this;
},
next() {
while (iteratorStack.length > 0) {
const currentIterator = last(iteratorStack);
const { value, done } = currentIterator.next();
if (done) {
iteratorStack.pop();
continue;
}
if (isFlatAble(value) && iteratorStack.length < depth + 1) {
iteratorStack.push(value[Symbol.iterator]());
continue;
}
return {
done: false,
value,
};
}
return {
done: true,
value: undefined,
};
},
};
}
function isIterable(valaue) {
return (
typeof (a === null || a === void 0 ? void 0 : value[Symbol.iterator]) ===
"function"
);
}
배열을 복사할 경우 스프레드 연산자가 유용한 이유가 뭘까?
일반 concat은 동일한 크기의 배열이 하나 더 생겨나는 차이가 있습니다. 만일 배열 내부의 값이 큰 문자열이라면 내부의 값도 모두 복사가 이루어져 메모리가 추가적으로 사용됩니다.
또한 지연된 concat은 총 크기가 100개로 합쳐질 이터레이터라고 하더라도 take(2, concat(…)) 처럼 필요한 갯수 만큼만 사용할 수 있는 추가적인 이점도 있습니다.
스프레드 연산자의 작동 원리
- 이터러블 객체의 확인 스프레드 연산자는 먼저 대상 객체가 이터러블인지 확인합니다.
- 객체가 이터러블일 경우
obj[Symbol.itertaor]()
메서드를 호출하여 이터레이터 객체를 얻습니다. - 얻은 객체의
next()
메서드를 반복적으로 호출합니다. 이 과정에서IteratorStep
과IteratorValue
추상 연산을 통해 수행됩니다. next()
호출로 반환된 각 객체에서value
속성을 추출합니다.- 앞서 얻은 값들을 새로운 컨텍스트 배열이나 함수 인자 목록들에 순서대로 삽입됩니다.
- 이터레이터가
done
속성이true
가 될 때까지 이 과정을 반복합니다. done
이true
인 이터레이터 결과 객체가 반환되면, 이후의next()
호출은 항상{done: true}
형태의 결과를 반환해야 합니다.
ECMASCRIPT의 표기 규칙
알고리즘
알고리즘에서 사용되는 변수에 대한 규칙은 다음과 같다. 선택적 매개 변수는 주변 대괄호
([, name])
로 표시되며 필수 매개 변수로서 사용이 된다. 나머지 매개 변수는 목록의 끝에 나타날 수 있으며 (, ...name)
으로 표시가 된다. 나머지 매개 변수는 필수 및 선택적 List로 캡쳐가 된다.Assert
로 시작하는 단계는 알고리즘의 불변 조건을 주장합니다. 이러한 주장은 그렇지 않으면 암시적인 알고리즘을 불변량을 명시적으로 만드는 용도로써 사용이 됩니다. 이 외의 추가적인 의미는 갖지 않으며 단순히 알로리즘을 명확히 하는데 사용이 됩니다.추상 연산
추상 연산이라고 불리는 연산은 알고리즘을 이해하는데 용이하게 하기 위하여 이름이 붙어지고 매개변수화된 함수의 형태로 작성이 됩니다. 이러한 이유는 다른 알고리즘에서 사용될 수 있게끔 만들기 위함이고 일반적으로 추상 연산은 일반적으로
OperationName(arg1, arg2)
와 같은 함수 적용 스타일을 참조하여제네레이터 함수가 유용한 이유
"즉시 에러 발생(fail-fast)" 원칙을 준수한다.
시스템에서 문제가 감지되면 즉시 작업을 중단하고 오류를 보고하는 방식을 사용을 준수합니다. 이러한 규칙을 지키는 것이 중요한 이유는 오류가 발생한 부분에 대한 결과가 다른 시스템으로 전파 되지 않고 다른 시스템에 영향을 주지 않는 다는 것을 보장할 수 있기 때문에 예측하기 쉬우며 디버깅할 시 유용하다는 장점이 있습니다.
제네레이터 함수 또한 고차 함수를 사용할 경우 겪게 되는 클로저에 대한 정보를 언제까지 저장할 것이고 언제 해제할 것이냐에 대한 문제를 동일하게 고려 해야 합니다. 하지만 고차함수와 다른 점이라고 할 경우 제네레이터 함수는 필요한 경우에만 값을 생성하기 때문에 특정한 경우에도 불필요한 자원을 사용함으로써 생기는 오버헤드를 걱정할 필요가 없습니다. 예를 들어 설명해보겠습니다.
function someFunction(value) {
let weight = 1;
while (true) {
value + 1;
}
}
위의 함수는 평소에 메서드를 작성할 경우 금지시되는
while(true)
문을 갖고 있습니다. 당연하게도 위의 함수는 스택 오버플로우가 발생할게 분명합니다. 이를 Generator
를 사용하여 다음과 같이 수정할 수 있습니다.function* someGenerator(value) {
let weight = 1;
while (true) {
yield value + weight++;
}
}
const generator = someGenerator(1);
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3
console.log(generator.next().value); // 4
// 필요한 만큼만 값을 생성하고 사용할 수 있습니다.
위의 예시에서
someGenerator
함수는 제네레이터 함수로 정의되어 있습니다. while (true)
루프 내에서 yield
키워드를 사용하여 값을 하나씩 생성합니다. 이렇게 하면 호출 시점에 필요한 값만을 생성할 수 있어 무한 루프로 인한 스택 오버플로우를 방지할 수 있습니다. 또한, 제네레이터를 사용하면 다음 값을 생성할 때마다 상태를 유지할 수 있어 메모리 효율성을 높일 수 있습니다.제네레이터 함수의 추가적인 장점
-
비동기 코드의 단순화 제네레이터는 비동기 흐름을 동기식 코드처럼 작성할 수 있게 도와줍니다.
async
/await
문법과 함께 사용하면 복잡한 비동기 로직을 더 쉽게 관리할 수 있습니다. -
이터러블 프로토콜과의 통합 제네레이터는 이터러블 프로토콜을 준수하므로, 배열이나 다른 이터러블 객체와 쉽게 통합하여 사용할 수 있습니다. 이를 통해 다양한 데이터 소스를 효율적으로 처리할 수 있습니다.
-
코드의 가독성 향상 복잡한 반복 로직이나 상태 관리를 단순화하여 코드의 가독성을 높입니다. 이는 유지보수성과 확장성을 향상시키는 데 기여합니다.
결론
이번 글에서는 Iterator와 Generator가 자바스크립트에서 어떻게 유용하게 사용되는지에 대해 살펴보았습니다. Iterator는 데이터 순회를 효율적으로 처리할 수 있게 해주는 기본적인 프로토콜을 제공하며, Generator는 이를 더욱 강력하게 확장하여 비동기 코드 관리와 메모리 효율성을 높이는 데 기여합니다.