ES6

生成器和迭代器

2022-05-19  本文已影响0人  咸鱼不咸_123

一、迭代器

1.什么是迭代器

迭代器(iterator),是使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。【迭代器本身是一个对象】

从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。

在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)

next方法有如下的要求:

const names=["abc","cba","nba"];


// * 创建一个迭代器对象来访问数组
let index=0;
const namesIterator={
  next:function(){
    // return {done:false,"abc"};
    // return {done:false,"cba"};
    // return {done:false,"nba"};
    // return {done:true,undefined};
    if(index<names.length){
      return {value:names[index++],done:false};
    }else{
      return {value:undefined,done:true};
    }
  }
}
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

1.1 生成迭代器的函数

// * 其实数组本身包含迭代器,只是我们想要实现这个思想
const names=["wjy","hyz","tqy"];
const nums=[1,2,3,4,5]

// * 这些都有限迭代器
function createArrayIterator(arr){
  let index=0;
  let iterator={
    index:0,
    next(){
      if(index<arr.length){
        return {done:false,value:arr[index++]}
      }else{
        return {done:true,value:undefined};
      }
    }
  }
  return iterator;
}

let namesIterator=createArrayIterator(names);
let numsIterator=createArrayIterator(nums);
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

console.log("-----分割线------------");
console.log(numsIterator.next());
console.log(numsIterator.next());
console.log(numsIterator.next());
console.log(numsIterator.next());
console.log(numsIterator.next());

console.log("----------无限迭代器--------");

// * 创建一个无限的迭代器
function createNumberIterator()
{
  let index=0;
  return {
    next:function(){
      return {done:false,value:index++}
    }
  }
}

const numberIterator=createNumberIterator();
console.log(numberIterator.next());

2.可迭代对象

但是上面的代码整体来说有点奇怪的:

什么是可迭代对象呢?

那我们要问一个问题,我们转成这样的一个东西有什么好处呢?

每次调用都是生成一个新的迭代器

// *可迭代对象是符合iterable protocol协议,它是一个对象,
// * 这个对象实现了@@iterator,在代码中我们可以使用Symbol.iterator去访问该属性,这个属性本身是一个函数,返回一个迭代器

const iterableObj={
 names:["wjy","hyz","tqy"],
 [Symbol.iterator]:function(){
   let index=0;
   return {
    next:()=>{
      if(index<this.names.length){
        return {done:false,value:this.names[index++]}
      }else{
        return {done:true,value:undefined};
      }
    }
  }
 }
}
// * iterableObj就是一个可迭代的对象

// * 生成了一个迭代器,每次调用都是产生了一个新的迭代器对象
let iterator=iterableObj[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

console.log("-------------分割线-------");

let iterator2=iterableObj[Symbol.iterator]();

console.log(iterator2.next());
console.log(iterator2.next());
console.log(iterator2.next());

2.1 for...of只遍历可迭代的对象

for...of只遍历可迭代的对象,例如下面的变量obj就是一个普通的变量,不是一个可迭代对象,所以会报错。

// * for……of可以遍历的东西必须是一个可迭代的对象
const obj={
  name:"wjy",
  age:18
}

// * 普通的对象是不支持for……of的,因为它不是一个可迭代对象
for(const item of obj){
  console.log("item:",item);
}

使用for...of遍历iterableObj对象,它会调用@@iterator方法,那它什么时候会停止呢?

当done为true时,就会停止。

每次取的是value的值。

const iterableObj={
 names:["wjy","hyz","tqy"],
 [Symbol.iterator]:function(){
   let index=0;
   return {
    next:()=>{
      if(index<this.names.length){
        return {done:false,value:this.names[index++]}
      }else{
        return {done:true,value:undefined};
      }
    }
  }
 }
}

for(const item of iterableObj){
  console.log("item:",item);
}

3.原生迭代器对象

事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:

//  * Array、String、Map、Set、NodeList的实例对象就是一个可迭代对象
const names=["wjy","hyz","tqy"];

// const iterator=names[Symbol.iterator]();
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());

for(const item of names){
  console.log(item);
}

console.log("-----------分割线------------");

// * Set/Map
const set=new Set();
set.add("java");
set.add("python");
set.add("javascript");

console.log(set[Symbol.iterator]);
for(const item of set){
  console.log(item);
}
console.log("----------函数的arguements----------");

// * 函数的arguments也是一个可迭代的对象
function foo(x,y,z){
  console.log(arguments[Symbol.iterator]);
  for(const item of arguments){
    console.log("参数:",item);
  }
}

foo(10,20,30,50)

4.可迭代对象的应用

那么这些东西可以被用在哪里呢?

这些方法本质上是调用迭代器的next方法

const iterableObj={
  names:["wjy","hyz","tqy"],
  [Symbol.iterator]:function(){
    let index=0;
    return {
     next:()=>{
       if(index<this.names.length){
         return {done:false,value:this.names[index++]}
       }else{
         return {done:true,value:undefined};
       }
     }
   }
  }
 }

//  * 展开运算符
 const names=["wjy","hyz","tqy"];
 const newNames=[...names,...iterableObj];
 console.log(newNames);


 const obj={
   name:"ysc",
   age:20
 }
//  * ES9 新增的一个特性:用的不是一个迭代器
 const newObj={...obj};
 console.log(newObj);

 /**
  * * 解构
  */

 const[name1,name2]=names; // * 使用的是iterator.next()方法取出值
 const {name,age}=obj;//* 不一样,使用的ES9新增的特性。

// * 创建一些其他对象时
const set=new Set(iterableObj);
const set2=new Set(names);
console.log(set);
console.log(set2);

/**
 * * 数组 Array.from([iterable])
 */
const arr1=Array.from(set);
console.log("arr1:",arr1);

/**
 * * Promise
 */
// * 如果可迭代对象中的value是一个普通的值,而不是一个Promise,其实实际执行的是Promise.resolve(value)
Promise.all(iterableObj).then(res=>{
  console.log(res);
})

5. 自定义类的迭代

在前面我们可以看到Array、Set、Map、等类创建出来的对象都是可迭代对象

案例:创建一个calssroom方法



// * 创建一个classroom类,这个类创建出来的对象都是可迭代对象
class Classroom{
  constructor(address,name,students){
    this.address=address;
    this.name=name;
    this.students=students;
  }
  entry(newStudent){
    this.students.push(newStudent)
  }
  [Symbol.iterator](){
    let index=0;
    return {
      next:()=>{
        if(index<this.students.length){
          return {done:false,value:this.students[index++]};
        }else{
          return {done:true,value:undefined};
        }
       
      },
      return:()=>{
        // * 这个可以监听迭代器的终止
        console.log("迭代器提前终止了");
        return {done:true,value:undefined}
      }
    }
  }
}

const classroom=new Classroom("厚德楼A栋","311",["james","kobe","curry","why"]);
classroom.entry("lilei")
for(const item of classroom){
  console.log(item);
  if(item=="james") break;
}

5.1 迭代器的中断

迭代器在某些情况下会在没有完全迭代的情况下中断:

那么这个时候我们想要监听中断的话,可以添加eturn方法

image-20220516203348619.png

二、生成器

/**
 * * 生成器函数是一个函数,但和普通函数有一些区别
 * * 1.需要在function加 *
 * * 2. 可以使用yield关键字控制函数的执行流程
 * * 3. 生成器函数返回的其实是一个生成器(Generator)
 */
function* foo(){
  console.log("函数开始执行");
  const value1=100;
  console.log(value1);
  yield
  const value2=200;
  console.log(value2);
  yield
  const value3=300;
  console.log(value3);
  yield
  console.log("函数执行结束");
}

let g=foo();//* 执行生成器函数时,其实里面的一行代码都不会执行,所以不会有任何的打印和输出,但会返回一个生成器
console.log(g);
// * 执行第一段代码
g.next()
console.log("----------分割线--------");
// * 执行第二段代码
g.next()
console.log("----------分割线--------");

// * 执行第三段代码
g.next()
console.log("----------分割线--------");

// * 执行第四段代码
g.next()

1.生成器函数的执行

/**
 * * 当遇到yield的时候,是暂停函数的执行
 * * 当遇到return的时候,是结束函数的执行,将done变为true,如果后面有代码都不会执行。
 */

/**
 * * yield后面可以添加一个表达式,后面作为本次next的value的值
 */
function* foo(){
  console.log("函数开始执行");
  const value1=100;
  console.log(value1);
  // return value1;//* return是一个特殊的yield,它就会将done变为true,后面的代码都不会执行了。
  // console.log("继续执行代码");
  yield value1;
  const value2=200;
  console.log(value2);
  yield value2;
  const value3=300;
  console.log(value3);
  yield value3
  console.log("函数执行结束");
  return "123"
}


/**
 * * 生成器是一个特殊的迭代器
 *  * next()函数会返回一个对象 {value:"",done:false/true}
 */
const generator=foo()
console.log("返回值1:",generator.next());
console.log("返回值2:",generator.next());
console.log("返回值3:",generator.next());
console.log("返回值4:",generator.next());

yield和return的区别:

2. next方法传递参数

函数既然可以暂停分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?

function* foo(){
  console.log("函数开始执行");
  const value1=100;
  console.log(value1);
  const n=yield value1;
  
  const value2=200*n;
  console.log(value2);
  const count=yield value2;

  const value3=300*count;
  console.log(value3);
  yield value3;

  console.log("函数执行结束");
  return "123"
}

const generator=foo();

// * 生成器的next可以传递参数
// * next()传入的参数会作为上一个yield的返回值
console.log(generator.next());

// * 第二段代码的执行是第二次next的执行
console.log(generator.next(10));

// * 第三段代码的执行是第三次next的执行
console.log(generator.next(20));

3. return方法终止执行

还可以调用return方法:它会终止函数的执行,将传递的参数作为value的值,done设置为true

之后调用next不会继承生成值了,只会生成 {value:undefined,done:true}

function* foo(){
  console.log("函数开始执行");
  const value1=100;
  console.log("第一段代码:",value1);
  const n=yield value1;
  
  const value2=200*n;
  console.log("第二段代码:",value2);
  const count=yield value2;

  const value3=300*count;
  console.log("第三段代码:",value3);
  yield value3;

  console.log("函数执行结束");
  return "123"
}

const generator=foo();
console.log(generator.next());

// * 第二段代码执行:使用了return:终止函数执行
// * 那么就意味着相当于在第一段代码的后面添加了 return 参数,执行了return操作会将参数作为value的值,done设置为true
// * 后面的代码都不会执行了。
console.log(generator.return(10));
image-20220516220727626.png

4.trow方法抛出异常

function* foo(){
  console.log("代码开始执行~");
   const  value1=100;
  try{
    yield value1;
  }catch(err){
    console.log("捕获到异常情况:",err);
    yield "abc"
  }

   const value2=200;
   yield value2; 

  console.log("代码执行结束~");
}
const generator=foo()

const result=generator.next();
// * 第二段代码不会执行 
if(result.value!==200){
  console.log(generator.throw("error message"));
}
// * 如果你将异常捕获,代码可以继续执行,如果没有捕获,则是不可以的。

5. 生成器替代迭代器

因为生产器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器替代迭代器。

function* createArrayIterator(arr){
  
  // * 第一种写法
  // yield "wjy";
  // yield "hyz";
  // yield "tqy";

  // * 第二种写法
  // for(const item of arr){
  //   yield item;
  // }

  // * 第三种写法 yield*,后面跟一个可迭代的对象
  yield* arr;


}

const names=["wjy","hyz","tqy"];
const namesIterator=createArrayIterator(names);
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

/**
 * * yield * 
 */

创建一个函数,这个函数可以遍历一个范围内的数字

// * 2. 创建一个函数,这个函数可以迭代一个范围内的数字
function* createRangeIterator(start,end){
  let index=start;
  while(index<end){
    yield index++;
  }
  // let index=start;
  // return {
  //   next:function(){
  //     if(start<end){
  //       return {done:false,value:start++};
  //     }else{
  //       return {done:true,value:undefined}
  //     }
  //   }
  // }
}
let  rangeIterator=createRangeIterator(2,10);
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());

Classroom的Symbol.iterator方法改写,里面使用生成器来完成迭代。

// * classroom案例

class Classroom{
  constructor(address,name,students){
    this.address=address;
    this.name=name;
    this.students=students;
  }
  entry(newStudent){
    this.students.push(newStudent)
  }
 foo=()=>{
   console.log("foo function");
 }
//  [Symbol.iterator]=function*(){
//   yield* this.students;
//  }
  *[Symbol.iterator](){
  yield* this.students;
  }
} 

const classroom=new Classroom("厚德楼","311",["hyz","wjy"])
// const classroomIterator=classroom[Symbol.iterator]();
// console.log(classroomIterator.next());

for(const item of classroom){
  console.log(item);
}

6.异步处理方案

function requestData(url){
  // 模拟网络请求
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve(url);
    },3000);
  })
}

需求:

// * 多次调用
// * 1.url:why->res:why
// * 2.url:res+"aaa"->res:whyaaa
// * 3.url: res+"bbb"=>res:whyaaabbb

6.1 回调地狱

// * 回调函数嵌套回调函数,回调地狱,代码可读性差
// * 第一种方案:多次回调
requestData("why").then(res=>{
  requestData(res+"aaa").then(res=>{
    requestData(res+"bbb").then(res=>{
      console.log("最终:",res);
    })
  })
})

6.2 Promise.then的返回值来实现

requestData("why").then(res=>{
  return requestData(res+"aaa");
}).then(res=>{
  return requestData(res+"bbb");
}).then(res=>{
  console.log("最终:",res);
})

6.3 Promise和generator的实现

function* getData(){ //* 封装成一个生成器函数
  const res1=yield requestData("why");
  const res2=yield requestData(res1+"aaa");
  const res3=yield requestData(res2+"bbb");
  console.log(res3);
}
// * 手动执行生成器函数
const generator=getData();//* 生成器函数返回一个生成器
generator.next().value.then(res=>{
  generator.next(res).value.then(res=>{
    generator.next(res).value.then(res=>{
      console.log(res);
    })
  })
})

改写了一个自动执行生成器函数:

function exeGenerator(genFn){
  const generator=genFn();
  function exec(res){
    const result=generator.next(res);
    if(result.done) return result.value;
    result.value.then(res=>{
      // console.log(res);
      exec(res);
    })
  }
  exec();
}
exeGenerator(getData)

或使用第三方库:co

// * 第三方包co自动执行
const co=require("co");
co(getData)

6.4 async和await

// * 第四种方案: async/await:本质上是generator和Promise的语法糖
async function getData(){ //* 封装成一个生成器函数
  const res1=await requestData("why");
  const res2=await requestData(res1+"bbb");
  const res3=await requestData(res2+"ccc");
  console.log(res3);
}
getData()

三、总结

生成器和迭代器.png
上一篇 下一篇

猜你喜欢

热点阅读