生成器Generator

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

function* helloWorldGenerator() {
    yield 'hello';
  	yield 'world';
  	return 'ending';
}

定义了⼀个⽣成器,仅仅需要在关键字function后⾯加上⼀个星号(*)。⽣成器函数体内就能够使用新关键字yield,从而生成独⽴的值。Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是迭代器对象(Iterator Object)。

const hw = helloWorldGenerator();  //调⽤⽣成器得到⼀个迭代器

hw.next(); //  {value: "hello", done: false}
hw.next(); //  {value: "world", done: false}
hw.next(); //  {value: "ending", done: false}
hw.next(); //  {value: undefined, done: true}

迭代器⽤于控制生成器函数的执行。迭代器对象暴露的最基本接⼝是next⽅法。每次调用next方法,生成器就开始执⾏代码,当代码执⾏到yield关键字时,就会生成⼀个中间结果(⽣成值序列中的⼀项),然后返回⼀个新对象,其中封装了结果值和⼀个指示完成的指示器。

每当⽣成⼀个当前值后,迭代器就到下一个yield表达式停住,随后耐⼼等待下⼀次值请求的到达。

第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hellodone属性的值false,表示遍历还没有结束。

第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值worlddone属性的值false,表示遍历还没有结束。

第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefineddone属性为true。以后再调用next方法,返回的都是这个值。

yield* 表达式

function* WarriorGenerator() {
    yield "Sun Tzu";
    yield* NinjaGenerator(); // yield* 将执⾏权交给了另⼀个⽣成器
    yield "Genghis Khan";
}
function* NinjaGenerator() {
    yield "Hattori";
    yield "Yoshi";
}
for (let warrior of WarriorGenerator()) {
    console.log(warrior);
}
// "Sun Tzu"  "Hattori"  "Yoshi"  "Genghis Khan"

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

在迭代器上使⽤yield*操作符,程序会跳转到另外⼀个生成器上执行。本例中,程序从WarriorGenerator跳转到⼀个新的NinjaGenerator⽣成器上,每次调⽤WarriorGenerator返回迭代器的next方法,都会使执行重新寻址到了NinjaGenerator上。

使用生成器

用生成器生成ID序列
function* IdGenerator() {//定义⽣成器函数IdGenerator
    let id = 0;//⼀个始终记录ID的变量,这个变量⽆法在⽣成器外部改变
    while (true) {
        yield ++id;
    }//循环⽣成⽆限⻓度的ID序列 }
}
const idIterator = IdGenerator();//这个迭代器我们能够向⽣成器请求新的ID值
const ninja1 = { id: idIterator.next().value };
const ninja2 = { id: idIterator.next().value };
const ninja3 = { id: idIterator.next().value };//请求3个新ID值

我们能够调⽤idIterator.next()⽅法来控制⽣成器执⾏。每当遇到⼀次yield语句⽣成器就会停⽌执⾏,返回⼀个新的ID值可以⽤于给我们的对象赋值。代码中没有任何会被不小心修改的全局变量。使用while无限循环会在每次遇到yield表达式停住。

使用迭代器遍历DOM树

通过递归回调的方式遍历

<div id="subTree">
    <form>
        <input type="text" />
    </form>
    <p>Paragraph</p>
    <span>Span</span>
</div>
<script>
    function traverseDOM(element, callback) {
        callback(element);// ⽤回调函数处理当前节点
        element = element.firstElementChild;
        while (element) {
            traverseDOM(element, callback);
            element = element.nextElementSibling;
        }// 遍历每个⼦树
    }
    const subTree = document.getElementById("subTree");
    traverseDOM(subTree, function (element) {
    });// 通过调⽤traverseDOM⽅法从根节点开始遍历
</script>

通过生成器递归遍历

function* DomTraversal(element) {
    yield element;
    element = element.firstElementChild;
    while (element) {
        yield* DomTraversal(element); // ⽤yield将迭代控制转移到另⼀个DomTraversal⽣成器实例上
        element = element.nextElementSibling;
    }
}
const subTree = document.getElementById("subTree");
for (const element of DomTraversal(subTree)) {
    console.log(element);
} // 使⽤for-of对节点进行循环迭代

我们在不使用回调函数的情况下,使用生成器解耦代码,从而将生产者(html节点)的代码和消费者(for-of循环打印、访问过的节点)的代码分割开。

与生成器交互

next方法参数
function* NinjaGenerator(action) { //⽣成器可以像其他函数⼀样接收标准参数
    const imposter = yield("Hattori " + action);
    console.log(imposter);
    yield("Yoshi (" + imposter + ") " + action); //传递回的值将成为yield表达式的返回值,因此impostrer的值是第二次调用next传递的值
}

const ninjaIterator = NinjaGenerator("skulk"); //普通的参数传递
const result1 = ninjaIterator.next(); // {value: "Hattori skulk", done: false}
const result2 = ninjaIterator.next("Hanzo"); // {value: "Yoshi (Hanzo) skulk", done: false}
// or
const ninjaIterator = NinjaGenerator("skulk"); //普通的参数传递
const result1 = ninjaIterator.next(); // {value: "Hattori skulk", done: false}
const result2 = ninjaIterator.next(); // {value: "Yoshi (undefined) skulk", done: false}

next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

向生成器抛出异常
function* NinjaGenerator() {
    try {
        yield 'Hattori';
        yield 'Hanzo';
    } catch (e) {
        console.log('内部捕获', e);
    }
}
const ninjaIterator = NinjaGenerator();
const result1 = ninjaIterator.next();

try {
    ninjaIterator.throw('a');
    ninjaIterator.throw('b');
} catch (e) {
    console.log('外部捕获', e);
}
//内部捕获 a
//外部捕获 b

遍历器对象ninjaIterator连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。ninjaIterator第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。throw方法也可以接受一个参数,该参数会被catch语句接收。

return()方法
function* NinjaGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const ninjaIterator = NinjaGenerator();

ninjaIterator.next()        // { value: 1, done: false }
ninjaIterator.return('foo') // { value: "foo", done: true }
ninjaIterator.next()        // { value: undefined, done: true }

遍历器对象ninjaIterator调用return()方法后,返回值的value属性就是return()方法的参数foo。并且,Generator 函数的遍历就终止了,返回值的done属性为true,以后再调用next()方法,done属性总是返回true

通过执行上下文跟踪生成器函数

function* NinjaGenerator(action) {
yield "Hattori " + action;
return "Yoshi " + action;
}
const ninjaIterator = NinjaGenerator("skulk");
const result1 = ninjaIterator.next();
const result2 = ninjaIterator.next();

一般情况下当程序从⼀个标准函数返回后,对应的执行环境山下文会从栈中弹出,并被完整地销毁。但在生成器中不是这样。

相对应的NinjaGenerator会从栈中弹出,但由于ninjaIterator还保存着对它的引⽤,所以它不会被销毁。可以把它看作⼀种类似闭包的事物。以这种⽅式,我们能保证只要函数还存在,环境及变量就都存在着。

const result1 = ninjaIterator.next();

如果这只是⼀个普通的函数调⽤,这个语句会创建⼀个新的next()的执行环境上下文项,并放入栈中。但生成器绝不标准。它会重新激活对应的执行上下文。在这个例⼦中,是NinjaGenerator上下文,并把该上下文放⼊栈的顶部,从它上次离开的地方继续执行。

const result2 = ninjaIterator.next();

在这个位置,又把整个流程走了⼀遍,首先通过ninjaIterator激活NinjaGenerator的上下文引用,将其入栈,在上次离开的位置继续执行。这次遇到了return语句。这个语句会返回值Yoshi skulk并结束生成器的执行,随之生成器进入结束状态。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值