什么是生成器函数
生成器函数是一个状态机,封装了多个内部状态。
生成器函数可以生成(返回)生成器对象,生成器对象同时也是迭代器对象。
生成器函数就是一个普通函数,不过它具有以下三个特点:
- 使用
function*
来声明。
- 当调用一个生成器函数时,函数主体并不马上执行,而是返回一个生成器对象,这个生成器对象同时也是迭代器对象。
- 每次当我们调用这个生成器对象的
next()
方法时,生成器函数会被执行到第一个yield
表达式。
下面是一个简单的生成器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function* sayNum () { console.log('begin...'); yield 1, yield 2, yield 3 } var iter = sayNum(); console.log(iter.next()); console.log(iter.next()); console.log(iter.next()); console.log(iter.next());
|
yield和yield*
yield
yield关键字用来使生成器函数暂停执行,并且返回跟在它后面的表达式的值。
yield关键字实际返回一个对象,包含value
和done
两个属性。
yield语句不能用在普通函数中,否则会报错。
yield*
yield*用来在一个生成器函数中执行另外一个生成器函数。比如以下代码:
1 2 3 4 5 6 7 8 9 10 11 12
| function* one () { yield 1, yield* two(), yield 4 } function* two () { yield 2, yield 3 } console.log(...one());
|
其实,yield*
后面可以跟任何一个迭代器对象。
1 2 3 4 5 6 7 8 9 10 11
| function* one () { yield 1, yield* [2,3], yield 4 } function* two () { yield 2, yield 3 } console.log(...one());
|
如果被代理的生成器函数有return语句,就可以向代理它的生成器函数返回数据。
1 2 3 4 5 6 7 8 9 10 11 12
| function* one () { yield 1; var a = yield* two(); yield (a + 1); } function* two () { yield 2; return 3; } console.log(...one());
|
生成器对象就是迭代器对象
调用生成器函数能生成一个迭代器对象。
调用任意一个可迭代对象的[Symbol.iterator]()
函数,就会返回一个迭代器对象。
所以,可以将生成器函数赋值给[Symbol.iterator]
属性,就可以使对象具有迭代器方法。
1 2 3 4 5 6 7 8 9 10 11 12 13
| var myIterator = {}; myIterator[Symbol.iterator] = () => range(5, 8); function* range (begin, end) { for (let i = begin; i <= end; i ++) { yield i; } } console.log(...myIterator);
|
next方法的参数
yield语句本身没有返回值,但是,next()方法可以带一个参数,作为上一个yield语句的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function* printNums () { var a = 2 * (yield 1); var b = a + (yield 2); var c = (yield 3); return (a + b + c); yield 4; } var itNums = printNums(); console.log(itNums.next().value); console.log(itNums.next(10).value); console.log(itNums.next(20).value); console.log(itNums.next(30).value); console.log(...printNums());
|
由于return语句执行时,返回对象的done为true,所以后面代码不会执行,并且for-of或者扩展运算符遍历被返回的迭代器对象时,也不会遍历done为true的对象的值。
生成器函数返回的迭代器对象,都有一个throw
方法,可以在函数体外抛出错误,下面为简单应用:
1 2 3 4 5 6 7 8 9 10 11
| function* gen () { try { yield 20; } catch (e) { console.log('Error caugth: ' + e); } } var iter = gen(); iter.next(); iter.throw(new Error('something wrong...'));
|
生成器函数返回的迭代器对象,还有一个return
方法,可以返回给定的值,并且终结生成器函数的继续执行。
1 2 3 4 5 6 7 8 9 10 11
| function* gen () { yield 1; yield 2; yield 3; } var iter = gen(); iter.next(); iter.return('xwj'); iter.next();
|
作为对象属性的Generator
1 2 3 4 5 6 7 8 9 10
| var xwj = { * one () { }, two: function* () { } };
|
简单应用
生成器函数可以暂停函数执行,并且返回任意表达式的值。这种特点使得Generator有多种应用场景:
异步操作的同步化表达
生成器的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next()方法再往下执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面。
yield语句是同步执行的。
下面是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| var getData = () => { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { iter.next(JSON.parse(xhr.responseText)); } else { console.log('error: ' + xhr.status); } } }; xhr.open('GET', 'http://192.168.0.101:8030', true); xhr.send(null); }; var render = (data) => { var items = document.getElementById('main').getElementsByTagName('p'); for (let item of items) { item.innerHTML = data[item.classList[0]]; } }; function* gen () { var result = yield getData(); render(result); } var iter = gen(); iter.next();
|
部署迭代器接口
利用生成器函数,我们可以在任意对象上部署迭代器接口。
因为生成器函数返回一个迭代器对象,只需要将讲这个迭代器对象,作为[Symbol.iterator]()
的返回值即可。
参考资料