迭代器(遍历器)
什么是迭代器?
首先迭代器是一个对象(它很特殊),这个对象可以遍历类数组的数据结构
其次这个对象里有两个方法
- next() //遍历数据结构里的成员
- return () //终止遍历
为什么要迭代器呢?
可以提高开发效率!开发者不需要知道数据结构的结构,就能按一定的顺序拿到数据结构里的数据。是不是很高级?
那要怎么要生成一个迭代器呢?
要知道怎么生成一个迭代器,先要知道什么是可迭代协议
大白话就说是,创建一个可迭代的对象时,这个可迭代对象必须要自己定义一个迭代器工厂函数,这样就可以利用自己的迭代器来遍历自己的数据了!
很多内置类型都是可迭代对象,意思就是说它们都有自己内置的迭代器!,比如
- 字符串
- 数组
- 映射
- 集合
- arguments对象
- NodeList等DOM集合类型
现在就来玩一玩迭代器!
let array=["abc","hello","hy","world"];
let arrayIterator=array[Symbol.iterator]();
/*
调用数组的迭代器工厂函数,生产出一个数组迭代器
*/
console.log(arrayIterator.next());//{ value: 'abc', done: false }
console.log(arrayIterator.next());//{ value: 'hello', done: false }
console.log(arrayIterator.next());//{ value: 'hy', done: false }
console.log(arrayIterator.next());//{ value: 'world', done: false }
console.log(arrayIterator.next());//{ value: undefined, done: true }
是不是很神奇呢!,上面代码还可以继续优化一下!
let array=["abc","hello","hy","world"];
let arrayIterator=array[Symbol.iterator]();
for(value of array){
console.log(value);
}
/*
abc
hello
hy
world
*/
console.log("------------------------------")
for(value of arrayIterator){
console.log(value);
}
/*
内置类型的迭代器本身也是一个可迭代对象,所以也能被for....of消费
abc
hello
hy
world
*/
讲一下为什么上面可以那样写
因为有一些原生语言特性实现了Iterator接口,只要实现了Iterator(迭代器)接口就能消费Iterable(可迭代对象),上面的for...of它本质上是一个迭代器,所以它可以消费数组(可迭代对象),其实不只for...of,还有以下的原生语言特性实现了Iterator接口
- 数组解构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all()接收期约组成的可迭代对象
- Promise.race()接收期约组成的可迭代对象
- yield*操作符,在生成器中使用
提前终止迭代器
使用迭代器对象的return()方法,可以停止迭代器遍历,但是要注意的是像数组这样的内置对象,即使你执行了return()方法,它也不会让迭代器关闭,而且它还会保留之前遍历的现场,等下次继续开遍历时,会接着上次遍历的地方,继续遍历下去。
下面上代码
let array=[1,2,3,4,5];
let arrayIterator=array[Symbol.iterator]();
arrayIterator.return=function(){
console.log("停止遍历");
return {done:true};
}
for(value of arrayIterator){
if(value >2){
break;
}
console.log(value);
}
/*
1
2
停止遍历
*/
console.log(arrayIterator.next());//{ value: 4, done: false }
console.log(arrayIterator.next());//{ value: 5, done: false }
console.log(arrayIterator.next());//{ value: undefined, done: true }
自定义一个迭代器
看完上面内置的迭代器后,自己是不是想实现一个迭代器呢^^
现在来说一下,实现一个自定义的迭代器要注意什么!
在迭代器工厂函数里要定义1个变量
- 变量index:保存迭代器指针的位置,这样下次执行遍历的时候,就会接着这个位置,遍历下一个数据(利用闭包,保存遍历现场)
let obj={
data:[0,1,3],
[Symbol.iterator](){
let index=0;//保存指针的位置
const self=this;
return {
next(){
if(index<self.data.length)
{
return {value:self.data[index++],done:false};
}
else
return {value:"undefined",done:true}
}
}
}
}
let objIterator =obj[Symbol.iterator]();
console.log(objIterator.next());//{ value: 0, done: false }
console.log(objIterator.next());//{ value: 1, done: false }
console.log(objIterator.next());//{ value: 3, done: false }
console.log(objIterator.next());//{ value: 'undefined', done: true }
看完图是不是清晰了很多^^
生成器
Generator(生成器) 函数是 ES6 提供的一种异步编程解决方案 ,它是一个状态机,还是一个迭代器生成函数。
调用一个Generator函数,会返回一个迭代器,该迭代器调用next()函数,可以遍历Generator函数中的状态。
Generator函数里,会出现yield关键字,每一个yield关键字就代表Generator函数的一个状态,只要迭代器遍历到yield关键字,生成器函数(Generator)就会中断执行,那什么时候它会再一次执行呢?等到迭代器再次调用next()方法时,生成器函数又会开始执行。
那下面就声明一个简单的Generator函数吧!
function * createGenerator(){
console.log("start");
yield 1;
yield 2;
yield 3;
return "ending";
}
let Generator=createGenerator();//生成了一个迭代器,虽然调用了生成器函数,但是它里面的代码是不会执行的,只有迭代器的next()方法才能驱动生成器函数执行内部的代码。
console.log(Generator.next());
/*
Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象(IteratorResult),它的value属性就是当前yield表达式的值1,done属性的值false,表示遍历还没有结束。
所以在"yield 1"语句前面的"console.log("start")"会执行。
*/
console.log(Generator.next());//{done:false,value:2}
console.log(Generator.next());//{done:false,value:3}
console.log(Generator.next());//{done:true,value:"ending"}
console.log(Generator.next());//{done:true,value:undefined}
console.log(Generator.next());//{done:tre,value:undefined}
生成器对象可以作为可迭代对象
废话不多说,直接上代码
function *generator(){
yield 1;
yield 2;
yield 3;
yield 4;
}
let generatorIterator=generator();//生成器函数返回一个迭代器(生成器对象)
let generatorIteratorIterator=generatorIterator[Symbol.iterator]();
/*
生成器函数返回的迭代器,本质上也是一个可迭代对象,因为它有一个默认迭代器,所以可以用for...of结构来消费生成器对象
*/
console.log(generatorIteratorIterator)
for(item of generatorIterator){
console.log(item);
}
/*
1
2
3
4
*/
//和下面的代码等价
console.log(generatorIterator.next().value);//1
console.log(generatorIterator.next().value);//2
console.log(generatorIterator.next().value);//3
console.log(generatorIterator.next().value);//4
使用yield实现输入和输出
从阮一峰大神上复制过来的一个例子,我觉得特别好理解
先说明一下,除非下一个next()方法给上一个yield表达式注入参数,否则yield表达式总是返回undefined(这里可能稍微有点难理解,等下我会上图^^)
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
console.log(a.next()); // Object{value:6, done:false}
console.log(a.next()); // Object{value:NaN, done:false}
console.log(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 }
- 声明了一个变量a,接收生成器foo返回的迭代器。
- 执行第一个a.next(),这时候第一个a.next()会输出一个IteratorResult对象包含了value(yield表达式的值)和done(遍历是否完成)两个属性,这里要特别注意的是!!!!变量y的赋值操作被中断了!!!
- 执行第二个a.next(),因为第二个a.next()方法没有向生成器函数注入实参,所以上一个yield表达式返回的是undifined,结果y=2*undefined,这就是为什么第二个a.next().value输出的值是NaN,之后变量z的赋值操作被中断。
- 执行第三个a.next(),因为第三个a.next()方法没有向生成器函数注入实参,所以上一个yield表达式返回的是undifined,结果z=undefined,这就是为什么第三个a.next().value输出的值是NaN
大家自己来模拟一下吧!
产生可迭代对象
可以使用星号增强yield的行为,让它能够迭代一个可迭代对象,直接上代码
function *Generator(){
yield *[1,2,3]
}
for(item of Generator()){
console.log(item);
}
//等价
function *Generator(){
yield 1;
yield 2;
yield 3;
}
for(item of Generator()){
console.log(item)
}
生成器作为默认迭代器
let obj={
data:[1,2,3],
[Symbol.iterator](){
let index=0;
let self=this;
return {
next(){
if(index<self.data.length){
return{
value:self.data[index++],
done:false
}
}
else
return{
value:undefined,
done:true
}
}
}
}
}
let obj1= {
data: [1, 2, 3],
[Symbol.iterator]:function *Generator(){
yield *this.data;
}
}
for(item of obj){
console.log(item);
}
/*
1
2
3
*/
for(item of obj1){
console.log(item);
}
/*
1
2
3
*/
看完上面的代码是不是觉得让生成器作为默认迭代器很方便!
提前终止生成器
return方法
使用生成器的return()方法可以提前终止生成器,关闭生成器以后,后续调用next()方法会显示done:true状态,而提供的任何返回值都不会被存储或传播,不废话了,直接上代码。
function *generator(){
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
yield 6;
yield 7;
yield 8;
yield 9;
}
let Iterator=generator();
console.log(Iterator.next());//{ value: 1, done: false }
console.log(Iterator.next());//{ value: 2, done: false }
console.log(Iterator.return("生成器关闭"));//{ value: '生成器关闭', done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
console.log(Iterator.next());//{ value: undefined, done: true }
throw方法
throw()方法会再暂停的时候将一个提供的错误注入到生成器对象中。如果错误未处理,生成器就会关闭,直接上代码。
function *generator(){
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
yield 6;
yield 7;
yield 8;
yield 9;
}
let Iterator=generator();
try {
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.throw("生成器关闭"));
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
}catch(e){
console.log(e);
}
/*
{ value: 1, done: false }
{ value: 2, done: false }
生成器关闭
*/
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
function *generator(){
try{
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
yield 6;
yield 7;
yield 8;
yield 9;
}catch(e){
console.log(e);
}
}
let Iterator=generator();
try {
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.throw("生成器关闭"));
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
console.log(Iterator.next());
}catch(e){
console.log(e);
}
/*
{ value: 1, done: false }
{ value: 2, done: false }
生成器关闭
{ value: undefined, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
*/
到这里生成器的内容就差不多了,下面来说一说生成器的一些应用场景
应用场景
协程场景
什么是协程?
我这里就不多说了,可以参考一下廖雪峰老师的文章,直接上代码
function cookTask(wash,cook){
let washIterator=wash();
let cookIterator=cook();
console.log(washIterator.next().value);
console.log(cookIterator.next().value);
console.log(washIterator.next().value);
console.log(cookIterator.next().value);
console.log(washIterator.next().value);
console.log(cookIterator.next().value);
}
function *wash(){
yield "我洗好了白菜";
yield "我洗好了猪肉";
yield "我洗好了鸡翅";
}
function *cook(){
yield '我煮好了白菜';
yield '我煮好了猪肉';
yield '我煮好了鸡翅';
}
cookTask(wash,cook);
/*
我洗好了白菜
我煮好了白菜
我洗好了猪肉
我煮好了猪肉
我洗好了鸡翅
我煮好了鸡翅
*/
我们这里模拟了炒菜的场景,这里有一个机器人厨师,首先这个机器人会执行洗菜的功能,当洗菜功能完成了,就将控制权让出给煮菜的功能,当煮菜的功能结束了,就将控制权让出给洗菜的功能。
异步操作同步化
这是阮一峰大神的一个例子
让ajax异步操作同步化的核心就是:Generator函数里的yield关键字,它可以中断执行!
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();
说一下思路
当迭代器it第一次调用了next()方法,启动生成器,ajax就会发起请求,当ajax完成时,通过回调函数,它就会再次启动next()方法,将接收到的数据,赋值给上一次yield表达式,于是变量result就收到了ajax发送过来的请求,最后把请求到的数据进行格式化并输出。
用Generator函数把异步任务同步化,是不是很神奇^^
结尾
这篇文章到这里就结束了,以上如果有什么内容是错误的,希望大佬们能指出^^
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!