본 포스팅은 유인동님의 강의 내용을 참조했습니다. 신세계네요 정말...
자바스크립트에서 함수는 일급 시민이다! 일급 시민이라는 말은 즉
라는 것인데, 함수를 리턴값으로 사용한다는 개념이 머리 속에 잘 들어오지 않았다. 이로 인해 2주차 함수형 프로그래밍에서 애를 먹은 것은 안비밀~🤣
이터러블과 이터레이터에 대해 공부를 하다 보면 필연적으로 만나게 되는 개념이 제너레이터이다. 다음에 나올 지연 평가에서 핵심적인 역할을 하는 제너레이터는 이터러블 프로토콜과 이터레이터 프로토콜을 둘 다 준수하며, 제너레이터 함수로부터 리턴된 객체를 의미한다...는데!!!!
The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.
출처
그럼 제너레이터 함수는 뭐냐?
The Generator function is....
Calling a generator function does not execute its body immediately; an iterator object for the function is returned instead. When the iterator's next() method is called, the generator function's body is executed until the first yield expression, which specifies the value to be returned from the iterator or, with yield, delegates to another generator function.. [출처](https://docs.w3cub.com/javascript/statements/function)
아...요약하자면, iterator의 next() 매서드가 호출될 때, 제너레이터는 그제서야 첫 번째 yield를 수행하고, 그렇지 않으면 계속 다른 제너레이터 함수로 위임한다는 것.
보통 배열을 순회할 때는, 각 배열의 원소 하나하나에 대한 즉시평가(계산)이 이루어진다. 하지만 iterator를 사용하면, 해당 평가를 지연시킴으로써 보다 효율적인 처리가 가능하다.
이터러블 프로토콜과 이터레이터 프로토콜의 설명과 예시코드는 다음 출처에 기반하여 작성하였다.
이터러블 프로토콜을 준수한 객체를 이터러블이라 부른다.
이터러블은 Symbol.iterator 메소드를 구현하거나 프로토타입 체인에 의해 상속한 객체를 말한다. Symbol.iterator 메소드는 이터레이터를 반환한다. 이터러블은 for…of 문에서 순회할 수 있으며 Spread 문법의 대상으로 사용할 수 있다.
배열은 Symbol.iterator매소드가 있지만, 일반 객체에는 존재하지 않는다. 따라서 배열은 이터러블이지만, 객체는 이터러블이 아니다.
const array = [1, 2, 3]; // 배열은 Symbol.iterator 메소드를 소유한다. // 따라서 배열은 이터러블 프로토콜을 준수한 이터러블이다. console.log(Symbol.iterator in array);//true
const obj = { a: 1, b: 2 }; console.log(Symbol.iterator in obj);//false
그 결과 배열은 for...of 문을 사용할 수 있지만, 객체는 for...of 문을 사용하여 순회할 수 없다.
// 배열은 이터러블 프로토콜을 준수한 이터러블이다. const array = [1, 2, 3]; // Symbol.iterator 메소드는 이터레이터를 반환한다. const iterator = array[Symbol.iterator](); // 이터레이터 프로토콜을 준수한 이터레이터는 next 메소드를 갖는다. console.log('next' in iterator); // true // 이터레이터의 next 메소드를 호출하면 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환한다. let iteratorResult = iterator.next(); console.log(iteratorResult); // {value: 1, done: false}
...A generator is very similar to a function that returns an array, in that a generator has parameters, can be called, and generates a sequence of values. However, instead of building an array containing all the values and returning them all at once, a generator yields the values one at a time, which requires less memory and allows the caller to get started processing the first few values immediately. In short, a generator looks like a function but behaves like an iterator. 출처
yield는 제너레이터 함수의 실행을 일시적으로 정지시키며, yield 뒤에 오는 표현식은 제너레이터의 caller에게 반환된다.출처
이 말을 쉽게 바꾸면, 함수가 특정 지점에서 끝나고 다음 실행 때는 끝난 시점에서 다시 시작된다는 말이다. yield로 잠시 제너레이터 함수의 동작을 멈추고, next()로 끝난 지점부터 재개하여 다음 yield까지 시행하는 것이다. 그리고 yield는 값을 object로 감싸서 던져준다.
//generator test //generator test function* generator (iter){ for(const a of iter){ yield a; } } const gen = generator([1,2,3,4,5]); console.log(generator.__proto__); console.log(gen.__proto__); console.log([...gen]);//[1,2,3,4,5]//이미 스프레드 연산자를 통해 generator 객체가 next()당하여 새 배열에 담김 console.log(gen.next())// 그래서 이렇게 스프레드 연산자로 출력 후, 다시 gen.next()를 시행하면 { value: undefined, done: true }가 나옴.
function* generateName() { yield 'S'; yield 'E'; yield 'U'; yield 'N'; yield 'G'; }
const genSpread = generateName(); console.log(genSpread.next());//{ value: 'S', done: false } console.log(genSpread.next());//{ value: 'E', done: false } console.log(genSpread.next());//{ value: 'U', done: false } console.log(genSpread.next());//{ value: 'N', done: false } console.log(genSpread.next());//{ value: 'G', done: false } console.log(genSpread.next());//{ value: undefined, done: true } console.log(genSpread.next());//{ value: undefined, done: true }
function* generator(i) { yield i; yield i + 10; } const gen = generator(10); console.log(gen.next().value); // expected output: 10 console.log(gen.next().value); // expected output: 20
var aGeneratorObject = function*(){ yield 1; yield 2; yield 3; }(); typeof aGeneratorObject.next; // "function", 이것은 next 메서드를 가지고 있기 때문에 iterator입니다. typeof aGeneratorObject[Symbol.iterator]; // "function", 이것은 @@iterator 메서드를 가지고 있기 때문에 iterable입니다. aGeneratorObject[Symbol.iterator]() === aGeneratorObject; // true, 이 Object의 @@iterator 메서드는 자기자신(iterator)을 리턴하는 것으로 보아 잘 정의된 iterable이라고 할 수 있습니다. [...aGeneratorObject]; // [1, 2, 3]
generator의 이와 같은 특징을 이용해서 평가를 지연하여 성능을 향상시킬 수도 있다.
https://armadillo-dev.github.io/javascript/whit-is-lazy-evaluation/ 참조
제너레이터의 핵심은 "요청이 왔을 때만 값을 준다는 것" 이다. 이를 이용해서 너무 큰 배열이 인풋으로 들어올 때의 메모리 부하를 줄일 수도 있고, 평가 횟수를 줄여 성능을 향상시킬 수도 있다.
너무나도 이질적인 개념이라 이해하는데 어려웠고, 사실 아직도 완벽하게 제너레이터를 이해하고 있다고 말할 수는 없다. 앞으로 작성할 코드에서 수시로 지연평가를 사용하면서 연습해 봐야겠다.
2주차 TIL이 너무나도 밀렸다. 내가 배운 것이 무엇인지도 명확하게 소화하지 못한 채로 과제를 시작해서 너무 많은 시간을 소비한 느낌이다. 지금 내가 모르는 것이 나중에 걸림돌로 작용하지 않도록 꼼꼼히 공부하는 것도 중요하지만 때로는 '지금은 잘 모르지만 다시 꼭 돌아와서 이 개념을 이해해야지' 하면서 잘 소화가 안되는 부분을 쿨하게 지나치는 연습도 해야겠다.
iter[Symbol.iterator]
라는 것은 즉,iter변수가 가지고 있는 객체의 속성 중에서 Symbol.iterator가 있냐는 말이다. 그러면 Symbol이라는 것은 무엇인가?
https://poiemaweb.com/es6-symbol#4-symbol-%EA%B0%9D%EC%B2%B4
이터러블의 Symbol.iterator를 프로퍼티 key로 사용한 메소드는 이터레이터를 반환한다.