生成器
- 关于为什么生成器对象就是迭代器问题的探索
- 对于生成器的举例解析
关于为什么生成器对象就是迭代器问题的探索
对于es6里面的生成器,最开始是看视频了解的,并不是特别深刻,只是知道生成器对象是迭代器。前几天复习的时候忽然在想生成器对象为什么可迭代,生成器对象是迭代器又从何而知呢?
查阅了一些关于迭代的东西(迭代协议),里面这么定义到。
作为 ECMAScript 2015 的一组补充规范,迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
迭代协议具体分为两个协议:可迭代协议和迭代器协议。
可迭代协议里提到
要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性
迭代器协议中也提到
只有实现了一个拥有以下语义(semantic)的 next() 方法,一个对象才能成为迭代器
文本的理解可能不够生动,我们来举个例子说明这个问题。
function * gen(){
console.log(111);
yield '一只没有耳朵';
console.log(222);
yield '一只没有尾部';
console.log(333);
yield '真奇怪';
console.log(444);
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
上述例子中,按照生成器对象就是迭代器这句话反推的话,那么 gen()
就是迭代器 gen
就是生成器函数。
那么在控制台去找,我们找到了生成器对象原型链的原型,以及迭代器原型链的原型链(如图所示)。
生成器原型链的原型:
迭代器原型链的原型链:
再在控制台输入下面这段代码:
gen.__proto__.prototype == gen().__proto__.__proto__
输出结果如下:
这样就可以得到生成器对象原型链的原型=迭代器原型链的原型链。
(ps:上述代码运行结果演示):
后面有看到书上说
执行Generator函数会返回一个遍历器对象。
所以上面的探索也可以用这个解释,不需要这么麻烦的思考过程(但是感觉会印象深刻一些)。
对于生成器的举例解析
Generator函数是一个普通的函数,但又两个特征:
- function命令与函数名之间有一个星号(*)
- 函数体内部使用
yield
语句定义不同的内部状态。
还是用例子来说明这个东西更加形象一些。
function * gen(arg){
console.log(arg);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
let iterator = gen('AAA');
console.log(iterator.next());
console.log(iterator.next('BBB'));
console.log(iterator.next('CCC'));
console.log(iterator.next('DDD'));
运行结果一并给出在来看这段代码的分析:
- 开始先传入
AAA
,但这个时候只是传参,并没有执行。 - 上面说到生成器对象就是迭代器。所以下面的
console.log(iterator.next())
运行的部分就是上面函数从开始到第一个yield
这部分,但是不进行上面let one 的赋值,返回111。function中打印出arg
也就是AAA,下面console.log(iterator.next())打印出的是object{value:111,done:false}。 console.log(iterator.next('BBB'))
这段传入的是BBB,执行的是上面函数第一个yield
到第二个yield
,给let one进行赋值操作,但不给let two进行赋值操作,并返回222。所以上面函数中打印one结果出来是BBB,下面console.log(iterator.next(‘BBB’))打印的是object{value:222,done:false}。- 下面基本都是这种类型,但是最后,
three
打印的是DDD,但这是没有yield
,也进行到了函数的结尾,这个时候返回的object则为object{value:undefined,done:true}。这里面的true表示迭代结束。
那么如果了解了上面这个例子,那我们把他进行变形再来看看
function * gen(arg){
console.log(arg);
let one = yield 111;
console.log(one);
console.log(yield 111);
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
//执行获取迭代器对象
let iterator = gen('AAA');
console.log(iterator.next());
console.log(iterator.next('BBB'));
console.log(iterator.next('CCC'));
console.log(iterator.next('DDD'));
这个例子中仅仅多了两行,但是理解确实有一定难度。
提醒一下,yield是一个关键字不能放在console.log中,否则会产生一定影响。
开始的时候额米有注意,但是后面看了一会发现用这个例子却更好理解一些。
- 第一步和上面的是一样的。
- 第二步中,传BBB时,这时候是从第一个
yield 111
到console.log(yield 111)
。这个时候是给 let one 赋值了,所以会出现BBB,但是内个console.log(yield 111)
是到这里截止,不会打印,但是返回了111,下面的console.log(iterator.next('BBB'))
会打印出object{value:111,done:false}。 - 第三步传CCC时,从第二个
yield
到第三个yield
,这里打印了console.log(yield 111)
,注意这个时候yield接收的是CCC,所以会打印CCC。这个后面打印了一次one,也就是BBB,返回了222,所以紧跟着打印的是object{value:222,done:false}。 - 这里可以发现出现了错位,所以到最后并没有打印出object{value:undefined,done:true}。
运行结果如下:
这里可以给最后一行加上
console.log(iterator.next('eee'));
这个时候就可以补上,并出现object{value:undefined,done:true}
多出来的运行结果如下:
关于上述两个问题,到这里我的理解就结束了,谢谢观看。