前面的话
前面介绍过Generator函数是一个状态机,里面封装了多个内部状态,并且只有调用对象的next()方法才能移到下一个状态。而每一个状态都是由yield语句开头的(或者return语句开头)。即只有调用next方法且内部指针指向该语句时,才会执行yield语句后面的表达式。所以yield语句表示一个状态,而yield*语句作用又不一样了,它功能相当于for…of的功能。
yield表达式
yield语句与return语句的区别与联系:
- 函数每次遇到yield语句就会暂停执行,下一次会从该位置继续向后执行;而return语句不具备记忆功能,执行到return语句函数执行结束
- 函数里面可以执行多条yield语句,但只能执行一次return语句
- yield语句与return语句都能返回紧跟在语句后面的表达式的值(yield语句本身没有返回值,只是把后面的表达式的赋给Generator对象调用next() 方法返回的对象的value属性)
- yield语句只能在Generator函数中,不能再其他函数中;return函数可以存在任意函数中。
- Generator函数可以不具有yield语句与return语句;不具有yield语句函数就变成了单纯的暂缓执行函数
举三个例子说明上面的情况:
function* gen() {
console.log('staring');
yield 'b';
yield 'c';
yield 'd';
return 'e';
yield 'ending'
}
var x = gen();
console.log( x.next());// staring {value: b,done:false}
console.log( x.next());// {value: c,done:false}
console.log( x.next());// {value: d,done:false}
console.log( x.next());// {value: e,done:true}
console.log( x.next());// {value: undefined,done:true}
这个例子可以说明上面的前三种情况。
(function foo() {
yield 1;
}())
在一个普通函数中使用yield语句,会报错。这一点说明上述的第4点,yield语句只能存在Generator函数中
function* g() {
console.log('x');
}
var ge = g();
setTimeout(() => {
ge.next();
}, 2000);
如果上述是一个普通函数,那么在变量ge被赋值时,就会执行。但函数是一个Generator函数,于是只有调用next方法才会执行。这个例子说明上述第5点
[yield表达式使用规则]
yield表达式如果用在另一个表达式中,必须放在圆括号里面;
function* demo() {
console.log('hello' + yield 123);
console.log('hello' + (yield 123));
}
如果表达式用作函数参数或者放在赋值表达式的右边,可以不加括号。
function* demo2() {
let input = yield 123;
}
for…of循环
for…of可以自动遍历Generator函数生成的遍历器对象,且此时不再需要调用next()方法
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for(let v of foo()){
console.log(v);
}
上面的例子使用for…of循环显示4条yield语句后面的值。一旦next()方法的返回对象的done属性为true,for…of循环就会终止,且不包含该返回对象。所以上述的return语句返回值不包括在for…of循环中。
利用Generator函数和for…of循环实现斐波那契数列
function* fibonacci() {
let [prev,curr] = [0, 1];
for(;;) {
[prev, curr] = [curr, prev+curr];
yield curr;
}
}
for(let n of fibonacci()) {
if(n > 10 ) break;
console.log(n)
}
[遍历对象]
利用for…of循环可以遍历任意对象的方法。原生的js对象没有遍历接口,无法直接使用for…of循环。但可以通过Generator函数为它加上这个接口。
function* objectEntries1(obj) {
let propKeys = Reflect.ownKeys(obj);
for(let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane1 = {first: 'Jane', last:"Doe"};
for(let [key, value] of objectEntries1(jane1)){
console.log(`${key} : ${value}`);
}
上面的原生jane对象不具备Iterator接口,无法使用for…of遍历。通过Generator函数objectEntries为它加上遍历器接口,这样就可以使用for…of遍历了。
还有一种写法是将Generator函数加到对象的Symbol.iterator属性上
function* objectEntries2() {
let propKeys = Object.keys(this);
for(let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane2 = {first: 'Jane', last:"Doe"};
jane2[Symbol.iterator] = objectEntries2;
for(let [key, value] of jane2){
console.log(`${key} : ${value}`);
}
}
除了for…of循环,扩展符(…)、解构赋值和Array.from方法内部调用的都是遍历器接口。所以,它们都可以将Generator函数返回的遍历器对象作为参数
function* numbers() {
yield 1;
yield 2;
yield 3;
return 4
}
// 扩展运算符
console.log( [...numbers()])
// 解构赋值
let [x, y, z] = numbers();
console.log([x,y,z]);
// Array.from
console.log( Array.from(numbers())) ;
yield*表达式
在Generator函数内部调用另一个Generator函数,默认情况下是没有效果的:
function* foo () {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for( let v of bar()) {
console.log(v);
}
在zoo和bar都是Generator函数,在bar里面调用foo是不会有效果的。
使用yield*语句,可以实现在一个Generator函数里面执行另一个Generator函数
function* foo () {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y'
}
for( let v of bar()) {
console.log(v);
}
等同于:
function* bar () {
yield 'x';
for(let v of foo()){
console.log(v);
}
yield 'y'
}
for( let v of bar()) {
console.log(v);
}
可以看到,yield*后面的Generator函数(没有return语句时)可以看做是for…of的一种简写形式。
当有return函数时,需要用var value = yield* generator()的形式获取return语句的值
function* foo() {
yield 2 ;
yield 3 ;
return 'foo';
}
function* bar () {
yield 1;
var v = yield* foo ();
console.log('v:' + v);
yield 4;
}
var a = bar();
console.log(a.next().value); // 1
console.log(a.next().value);// 2
console.log(a.next().value);// 3
console.log(a.next().value);// v: foo 4
console.log(a.next())// {value:undefined, done: true}
使用return语句的值需要使用新的变量来接受
function* gen() {
yield 'a';
yield 'b';
return 'the result';
}
function* log(generator) {
let result = yield* generator;
console.log(result)
}
// for(let value of log()){
// console.log(value);
// }
console.log([...log(gen())]);
上例存在两次遍历:第一次是扩展运算符遍历函数log返回的遍历器对象,第二次时yield*语句遍历函数gen返回的遍历器对象。
[yield*遍历数据]
任何数据只要有Iterator接口,就可以被yield*遍历
function* gen() {
yield* ['a', 'b', 'c'];
}
var k = gen();
console.log(k.next().value);// a
console.log(k.next().value);// b
console.log(k.next().value);// c
不加星号返回整个数组
function* gen1() {
yield ['a', 'b', 'c'];
}
console.log(gen1().next().value);// ['a', 'b', 'c']
字符串也可以被遍历,因为字符串也具有Iterator
function* gen2() {
yield* 'hello';
}
var y = gen2();
console.log(y.next().value) // h
console.log(y.next().value) // e
yield*命令可以很方便的取出嵌套数组的所有成员
const tree = ['a', ['b', 'c'], ['d', 'e']];
function* Tree(tree) {
if(Array.isArray(tree)) {
for(let i = 0; i < tree.length;i++) {
yield* Tree(tree[i])
}
}else {
yield tree;
}
}
for(let v of Tree(tree)) {
console.log(v);
}
使用yield*语句遍历完全二叉树
function eTree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// 中序遍历
function * order(t) {
if(t) {
yield* order(t.left);
yield t.label;
yield* order(t.right);
}
}
// 生成二叉树
function make(array) {
// 判断是否为叶子结点
if(array.length == 1) {
return new eTree(null, array[0], null);
}
return new eTree(make(array[0]),array[1], make(array[2]));
}
let etree = make([[['a'], 'b',['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];
for(let node of order(etree)){
result.push(node);
}
console.log(result);
}