当前位置:K88软件开发文章中心编程语言JavaScriptECMAScript → 文章内容

ECMAScript 6 Generator 函数

减小字体 增大字体 作者:佚名  来源:网上搜集  发布时间:2019-1-15 15:38:37

外一个例子。var arr = [1, [[2, 3], 4], [5, 6]];var flat = function* (a) { a.forEach(function (item) { if (typeof item !== 'number') { yield* flat(item); } else { yield item; } }};for (var f of flat(arr)){ console.log(f);}上面代码也会产生句法错误,因为forEach方法的参数是一个普通函数,但是在里面使用了yield语句(这个函数里面还使用了yield*语句,这里可以不用理会,详细说明见后文)。一种修改方法是改用for循环。var arr = [1, [[2, 3], 4], [5, 6]];var flat = function* (a) { var length = a.length; for (var i = 0; i < length; i++) { var item = a[i]; if (typeof item !== 'number') { yield* flat(item); } else { yield item; } }};for (var f of flat(arr)) { console.log(f);}// 1, 2, 3, 4, 5, 6另外,yield语句如果用在一个表达式之中,必须放在圆括号里面。console.log('Hello' + yield); // SyntaxErrorconsole.log('Hello' + yield 123); // SyntaxErrorconsole.log('Hello' + (yield)); // OKconsole.log('Hello' + (yield 123)); // OKyield语句用作函数参数或赋值表达式的右边,可以不加括号。foo(yield 'a', yield 'b'); // OKlet input = yield; // OK与Iterator接口的关系上一章说过,任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口。var myIterable = {};myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3;};[...myIterable] // [1, 2, 3]上面代码中,Generator函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了Iterator接口,可以被...运算符遍历了。Generator函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。function* gen(){ // some code}var g = gen();g[Symbol.iterator]() === g// true上面代码中,gen是一个Generator函数,调用它会生成一个遍历器对象g。它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己。next方法的参数yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。function* f() { for(var i=0; true; i++) { var reset = yield i; if(reset) { i = -1; } }}var g = f();g.next() // { value: 0, done: false }g.next() // { value: 1, done: false }g.next(true) // { value: 0, done: false }上面代码先定义了一个可以无限运行的Generator函数f,如果next方法没有参数,每次运行到yield语句,变量reset的值总是undefined。当next方法带一个参数true时,当前的变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。再看一个例子。function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z);}var a = foo(5);a.next() // Object{value:6, done:false}a.next() // Object{value:NaN, done:false}a.next() // Object{value:NaN, done:true}var b = foo(5);b.next() // { value:6, done:false }b.next(12) // { value:8, done:false }b.next(13) // { value:42, done:true }上面代码中,第二次运行next方法的时候不带参数,导致y的值等于2 * undefined(即NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield语句的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield语句的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。注意,由于next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法时,不能带有参数。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。如果想要第一次调用next方法时,就能够输入值,可以在Generator函数外面再包一层。function wrapper(generatorFunction) { return function (...args) { let generatorObject = generatorFunction(...args); generatorObject.next(); return generatorObject; };}const wrapped = wrapper(function* () { console.log(`First input: ${yield}`); return 'DONE';});wrapped().next('hello!')// First input: hello!上面代码中,Generator函数如果不用wrapper先包一层,是无法第一次调用next方法,就输入参数的。再看一个通过next方法的参数,向Generator函数内部输入值的例子。function* dataConsumer() { console.log('Started'); console.log(`1. ${yield}`); console.log(`2. ${yield}`); return 'result';}let genObj = dataConsumer();genObj.next();// StartedgenObj.next('a')// 1. agenObj.next('b')// 2. b上面代码是一个很直观的例子,每次通过next方法向Generator函数输入值,然后打印出来。for...of循环for...of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法。function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6;}for (let v of foo()) { console.log(v);}// 1 2 3 4 5上面代码使用for...of循环,依次显示5个yield语句的值。这里需要注意,一旦next方法的返

上一页  [1] [2] [3] [4] [5] [6] [7]  下一页


ECMAScript 6 Generator 函数