서론
실제로, 이전에 [黯羽轻揚:JavaScript 제네레이터](/articles/javascript 生成器/) 에서 이미 정리했지만, 여기는 고의로 중복하는 것이 아닙니다. 이전에는 MDN 문서를 참고하여 정리한 것으로, 문법 규칙에 중점을 두었습니다. 본문은 FireFox 중의 해당 특성의 실현자의 친술에 근거하여, 몇 가지 상세와 응용 시나리오를 보충합니다
중복을 피하기 위해, 본문에서는 문법 규칙 (function* + yield) 을 설명하지 않습니다. 문법 상세는 이전의 기사를 참조하십시오
一.작용 및 내부 원리
generator(제네레이터) 는 이터레이터를 생성하는 데 사용되며, 문법은 매우 간결합니다 (function* + yield)
제네레이터는 yield 문을 실행할 때, 제네레이터의 스택 구조 (로컬 변수, 파라미터, 임시 값, 제네레이터 내부의 현재의 실행 위치) 가 스택에서移出됩니다. 그러나 제네레이터 오브젝트는 이 스택 구조에 대한 참조 (백업) 를 보유하고 있으므로, 나중에 .next() 를 호출하면 스택 구조를 재활성화하여 계속 실행할 수 있습니다
예를 들어:
// 定義生成器
var gen = function*() {
console.log('before yield 1');
yield 1;
console.log('before yield 2');
yield 2;
}
// 调用生成器返回迭代器
var iter = gen();
iter.next(); // before yield 1
// Object {value: 1, done: false}
iter.next(); // before yield 2
// Object {value: 2, done: false}
iter.next(); // Object {value: undefined, done: true}
iter.next(); // Object {value: undefined, done: true}
yield 문은 함수 본체를 몇 개의 세그먼트로 분할하며, .next() 는 한 번에 1 세그먼트를 실행합니다
二.이터레이터와 제네레이터
function* 로 정의된 것은 이터레이터의 제네레이터 (약칭 제네레이터) 라고 불립니다. 그것을 호출하면 이터레이터를 반환하기 때문입니다
모든 제네레이터는 내장의 .next() 와 [Symbol.iterator]() 메서드의 구현을 가지고 있으며, 우리는 루프 부분의 행위만编写하면 됩니다. function* 후의 함수 본체는 루프 구조의 루프 본체와 같습니다. 예를 들어:
function* gen(arr) {
for (var i = 0; i < arr.length; i++) {
yield arr[i];
}
}
var iter = gen([1, 2, 4]);
console.log(iter.next()); // Object {value: 1, done: false}
console.log(iter.next()); // Object {value: 2, done: false}
그 중에서 gen 의 작용은 연속적인 배열을 "숨을 쉬는" 배열로 바꾸는 것으로, for 루프는 본래 멈출 수 없지만, yield 는 확실히 그것을 멈추게 했습니다. 이것도 제네레이터의一大특색입니다. 이特点을 이용하여很多有趣한 것을實現할 수 있습니다. 예를 들어 애니메이션으로快速排序의 과정을展示하는 등, 偽 코드는 다음과 같습니다:
function quicksort(arr) {
// sort
forloop {
updateSortedArr(); // 完成一趟排序
displaySortedArr(); // 展示本趟排序結果
}
return sortedArr;
}
물론, 이렇게 하면 애니메이션을 볼 수 없습니다. n 趟의排序는 순간에 완료되어 버리기 때문입니다 ( 너무 빨라서, 아무것도看不清啊 애니메이션 따위 없다,根本没변好吗). 변통하는 방법을很容易에 생각할 수 있습니다: 먼저每一趟의 결과를存めて두고, 마지막에展示합니다:
function quicksort(arr) {
var tmpArr = [];
forloop {
updateSortedArr(); // 完成一趟排序
tmpArr.push(sortedArr); // 存めて두기
}
// 애니메이션으로排序過程을展示
anim(tmpArr);
}
每一趟의 결과를 취득한 후, 어떻게展示하고 싶은지는 자유롭게 할 수 있습니다.感觉도 그다지费力하지 않습니다. 그렇다면, 만약 애니메이션으로快速排序의每一趟 2 개의 포인터의 이동을展示하고 싶은 경우는 어떻게 할까요? 우리는 또 포인터 이동의 tmpArr 를 기록할 필요가 있는 것 같습니다. 만약 이 배열이很大라면, 만약。。。이렇게 하면 반드시 더 많은 메모리 공간을 필요로 하여已经发生过일을 기록합니다
仔细로 생각해 보면,排序過程中, 애니메이션展示에 필요한 데이터는 이미 있습니다. 그렇다면,排序하면서 애니메이션展示하는 것은 가능할까요? 물론 가능합니다:
function* quicksort(arr) {
forloop {
yield sortedArr;
// 또는
// yield step;
}
}
var iter = quicksort(arr);
// 애니메이션으로排序過程을展示
function anim() {
display(iter.next());
setTimeout(anim, 300);
}
anim();
맞습니다, 제네레이터는 루프에 "숨을 쉬게" 할 수 있으며, 숨을 쉬는過程中에 애니메이션을 제조합니다
P.S. 실제로는 제네레이터가 없어도, 우리는 루프를 "멈추게" 할 수 있습니다. [黯羽轻揚:JavaScript 구현 yield](/articles/javascript 实现 yield/) 를 참조하십시오
P.S.快速排序의 구체적인 상세에 대해서는, 黯羽轻揚:排序算法之快速排序(Quicksort)解析 를 참조하십시오
三.특징
제네레이터의 특징은 다음과 같습니다:
-
普通の함수는自暂停할 수 없지만, 제네레이터함수는 할 수 있습니다
-
yield는function*의 직접作用域中에서만 유효하며,function*중의匿名함수중의yield는非法입니다 -
無限序列를 처리할 수 있습니다.無限大의 배열을構築하는 것은 불가능하지만, 제네레이터를 사용하여無限序列의構築規則을實現할 수 있으며, 無限序列를 처리할 수 있습니다
-
배열을 반환하는 다른思路를 제공합니다. 배열이 아닌 제네레이터를 반환하고, 시간으로 공간을바꿉니다
-
복잡한 루프를리팩토링할 수 있으며, 그것을 2 개의 부분으로拆分하고, 데이터생성부분을 제네레이터로변환한 후, for...of 로 이러한 데이터를遍历합니다
-
快速하게 이터레이터를製造할 수 있으며, 임의의 오브젝트를可迭代로 할 수 있습니다. 상세는 [黯羽轻揚:for…of 루프_ES6 노트 1](/articles/for-of 루프-es6 노트 1/) 를 참조하십시오
-
이터레이터를拡張하기 쉽고, 필터링 등의 조작을很容易에實現할 수 있습니다
제 2 점은주의가 필요합니다. 대부분의 제네레이터를소개하는 자료는 이점에언급하지 않지만, 우리는 확실히 제네레이터중에서 setTimeout 으로 yield 를지연시키는 것이 불가능합니다. 제네레이터로 이터레이터를拡張하는 것은不错的选择이며, 코드는 매우 자연스럽습니다. 예시는 다음과 같습니다:
// 扩展 이터레이터
function* filter(isValid, iterable) {
for (var val of iterable) {
if (isValid(val)) {
yield val;
}
}
}
// test
function isValid(val) {
return val > 1;
}
for (var val of filter(isValid, [0, 1, 2, 4])) {
console.log(val);
}
제네레이터로 이터레이터를포장하면,无缝衔接의美感이 있지 않나요?
四.高級技巧
###1. 외부에서 제네레이터의 로직流에 영향을 주다
이터레이터의 next(returnVal) 메서드는 옵션 파라미터를 받아들이며, 파라미터는 제네레이터중의上一条 yield 문의 반환값이 됩니다. 이렇게 호출자는 외부에서 제네레이터의 로직流에 영향을 줄 수 있습니다. 예를 들어:
function* gen() {
var water = yield 'give me a cup of pure water';
yield water.drink();
}
// test
var iter = gen();
console.log(iter.next());
console.log(iter.next({
name: 'pure water',
drink: function() {
return 'hmm, well';
}
}));
첫 번째 yield 는 호출자에게 한 잔의 물을요구하며, 두 번째 .next() 는 물을 제네레이터에渡し, 그 후 두 번째 yield 는 물을마십니다
이것은 双方向의交互過程으로, 실제 응용에서는, next() 의 반환값에 근거하여 제네레이터가 무엇을 필요로 하는지를 판단한 후, 그 후次の .next() 로 그것을渡し, 복잡한 로직을 제네레이터중에隔離하며, 호출하는 것은 대화처럼轻松합니다
###2. 이터레이터를 종료하다
주의, 이터레이터 입니다. 이터레이터를 종료하는 방법은 2 종류 있습니다:
-
이터레이터의
throw(err)메서드로, 효과는 제네레이터중의 yield 式가 함수를 호출하여 에러를던지는 것과 같습니다 -
이터레이터의
return(returnVal)메서드로, 옵션 파라미터를 받아들이며, 파라미터는 value 로서 반환됩니다 (done은true). 제네레이터는finally코드블록만 실행하고不再恢復執行합니다
throw 예시는 다음과 같습니다:
// throw
function* gen() {
try {
yield 1;
yield 2;
} catch (err) {
console.log('error occurs: ' + err);
} finally {
console.log('clean up');
}
}
var iter = gen();
console.log(iter.next()); // Object {value: 1, done: false}
console.log(iter.throw(new Error('err'))); // error occurs: Error: err
// clean up
// Object { value: undefined, done: true }
console.log(iter.next()); // Object {value: undefined, done: true}
throw(err) 는 이터레이터가異常에 닫히는 것을 나타내며, 제네레이터 내부에 finally 중의清理工作을 실행하는 것을通知하는 데 사용됩니다
한편, return() 은 이터레이터가正常에 닫히는 것을 나타냅니다. 예시는 다음과 같습니다:
console.log(iter.next()); // Object {value: 1, done: false}
console.log(iter.return('ok')); // clean up
// Object { value: "ok", done: true }
console.log(iter.next()); // Object {value: undefined, done: true}
주의: 'ok' 는 value 로서立即에 반환되며,次の .next() 의 때에 반환되는 것이 아닙니다
P.S.Chrome49 는 아직 return() 을 서포트하지 않습니다. FF 에서는 throw() 후에도 return() 할 수 있지만, 만약 먼저 return() 하고 그 후 throw() 하면 에러가 보고됩니다
###3. 이터레이터를拼接하다
yield* iter 는 이터레이터를拼接할 수 있으며, 1 개의 제네레이터중에서 다른 제네레이터를 호출하는 것을 서포트합니다. 예를 들어:
var gen1 = function* (){
yield 1;
yield 2;
}
var gen2 = function* (){
yield* gen1();
yield 3;
yield 4;
}
for (var val of gen2()) {
console.log(val); // 1 2 3 4
}
五.정리
제네레이터는執行流에 "숨을 쉬게" 할 수 있으며, 멈출 수 없는 것을暂停할 수 있고, 루프를리팩토링하는 데 사용할 수 있으며, 無限序列를驾驭할 수 있고, 이터레이터를포장할 수 있습니다。。。메리트는 많습니다
참고 자료
- 《ES6 in Depth》: InfoQ 中文站이 제공하는 무료 전자서적
아직 댓글이 없습니다