JavaScript(ES6): yield的迭代器(Iterator)和生成器(Generator)

迭代器(Iterator)和生成器(Generator)

前言

最早知道迭代器是在学java的时候,那时候遍历map只能使用迭代器。具体怎么使用我现在忘得差不多了。其他语言应该大部分都有迭代器,甚至SQL也有cursor(游标)

存在即有意义。

没有迭代器的日子

循环语句迭代数据的时候,需要初始化一个对象来临时记录这个值。

let array = [1,2,3];
for(let i = 0; i < array.length ; i++){
    var result = array[i];
}

如上述result既记录迭代过程中的每个数据。

**优点:**语法简单;非优点:需要自己处理下标、一次性循环完数据,没有阻塞。

感觉这几个点也不算什么优缺点,为什么要有迭代器和生成器呢?

一、 迭代器

1、迭代器是对象

迭代器内部有迭代专用接口和方法,所有的迭代器都会有一个 next方法,返回一个对象

function next(){
    return {
        value:[any],// 迭代的数据
        done:[boolean]//是否迭代完毕
    }
}

每次调用 next下标会一直往下移动,直到被迭代完毕,往后迭代都是最后一个值。

2、简易迭代器

var getIterator = function (array) {
  var index = 0;
  var length = array.length;
  return {
    next: function () {
      var done = index >= length;
      var value = done ? undefined : array[index++]
      return {
        done : done,
        value : value
      }
    }
  }
}

var iterator = getIterator(["A","B","C"]);
console.log(iterator.next());//{done: false, value: "A"}
console.log(iterator.next());//{done: false, value: "B"}
console.log(iterator.next());//{done: false, value: "C"}
console.log(iterator.next());//{done: true, value: undefined}
console.log(iterator.next());//{done: true, value: undefined}

以上是通用型对数组迭代,只适合特定结构的迭代。为了生成迭代器对象,ES6创造了生成器:

二、生成器

1、什么是生成器

生成器是返回迭代器的函数,通过 function后面加个*来表示,函数体内部使用yield来表示下次迭代出来的数据:

const getIterator = function *() {
  yield "A";
  yield "B";
  yield "C";
}
const iterator = getIterator();
console.log(iterator.next());//{done: false, value: "A"}
console.log(iterator.next());//{done: false, value: "B"}
console.log(iterator.next());//{done: false, value: "C"}
console.log(iterator.next());//{done: true, value: undefined}
console.log(iterator.next());//{done: true, value: undefined}

2、怎么用生成器

生成器本质上是一个返回迭代器的函数

  • function *()来表示这个函数,
  • yield相当于return,每调用一次就会结束使用掉一个“return”

利用类似return短路的特性,我们可以这样搞事情:

const getIterator = function *() {
  console.log("第1次执行next")
  yield "A";
  console.log("第2次执行next")
  yield "B";
  console.log("第3次执行next")
  yield "C";
}
console.log(iterator.next());//第1次执行next {done: false, value: "A"}
console.log(iterator.next());//第2次执行next {done: false, value: "B"}
console.log(iterator.next());//第3次执行next {done: false, value: "C"}
console.log(iterator.next());//{done: true, value: undefined} 难怪后面返回的是 undefined 可能是因为最后一次“yeild”返回的是undefined

造一个1.2简易迭代器的轮子:

const getIterator = function *(array) {
    for(let i = 0 ;i < array.length; i++){
       yield array[i];
    }
}

3、注意事项

  • yield只能在 生成器内部使用
const getIterator = function *(array) {
  array.forEach(function(item){
    yield item
  })
}

const getIterator = function *(array) {
  array.forEach(item => {
    yield item
  })
}

以上示例代码连webstorm的静态校验都过不去,因为这里yield在foreach的回调函数内部,因此报错了,即使是lambda表达式

  • 不能使用lambda创建生成器,如:
const getIterator =  *(array) => {
   yield 1
}
  • return 会终止迭代,提前结束迭代
const getIterator = function *() {
  console.log("第1次执行next")
  yield "A";
  console.log("第2次执行next")
  return "B";
  console.log("第3次执行next")
  yield "C";
}
iterator = getIterator()
console.log(iterator.next());//第1次执行next {value: "A", done: false}
console.log(iterator.next());//第2次执行next {value: "B", done: true}
console.log(iterator.next());//第3次执行next {value: undefined, done: true}

三、for-of 循环

1、迭代器的循环

迭代器都可以用for-of 来循环:

const getIterator = function *() {
  yield "A";
  yield "B";
  yield "C";
}
const iterator = getIterator()
for(let a of iterator){
  console.log(a)
}
// A B C

2、可迭代对象都可以使用for-of进行循环。

所有集合(ArraySetMap)和字符串都是可迭代对象:

const array = [1,2];
for(let a of array){
    console.log(a);
}
// 1 2
const str = "12";
for(let a of str){
    console.log(a);
}
// 1 2

因为这些对象内部实现了

Array.protptype[Symbol.iterator] = *function(){
    for(let data in this){
        yeild data
    }
}

也就是这些个对象是可迭代对象

造个类似轮子:

const myArray = function(array){
  this[Symbol.iterator] = function *(){
    for(let data in array){
      yield data
    }
  }
}

var  iterator = new myArray([1,2,3,4])
console.log(iterator)// myArray {Symbol(Symbol.iterator): ƒ}
for(let a of iterator){
  console.log(a)
}
//1 2 3 4

3、默认迭代器

  • 根据1可得for of 使用了迭代器
  • 根据2可得数组等可迭代对象有个默认的迭代器
    因此,我们可以这样搞:
const array = [1,2,3];
let iterator = array[Symbol.iterator]();
for(let a of iterator){
  console.log(a)
}
//1 2 3 
iterator = array[Symbol.iterator]();
 console.log(iterator.next());
 // {value : 1, done :false}

4、其他

迭代器都可以用for-of 来循环,每次循环前都会检查下是不是可迭代的内容。
校验的逻辑类似于:

const isIterator = Clazz => typeof Clazz.prototype[Symbol.iterator] === 'function';

但是也有失准的时候,比如:

const myArray = function(array){
  this[Symbol.iterator] = function *(){//迭代器方法是放在对象里面,而不是原型链上,但是它确实是可迭代对象
    for(let data in array){
      yield data
    }
  }
}
console.log(isIterator(myArray));//false

因此:

const isIterator = Clazz => {
   return typeof  new Clazz()[Symbol.iterator] === 'function';
}
console.log(isIterator(myArray));//true

四、高级迭代功能

1、迭代器传参

  • next方法传参的值会替换上次 yield返回的值:
let getIterator = function *() {
  console.log("第1次执行next")
  let first = yield "A";
  console.log("第2次执行next")
  let second = yield first + "B";
}
let iterator = getIterator();
console.log(iterator.next());//{value: "A", done: false}
console.log(iterator.next());//{value: "undefinedB", done: false}
console.log(iterator.next());//{value: undefined, done: true}

 iterator = getIterator();
console.log(iterator.next(1));//{value: "A", done: false}
console.log(iterator.next(2));//{value: "2B", done: false}
console.log(iterator.next(3));//{value: undefined, done: true}

getIterator = function *() {
  console.log("第1次执行next")
  let first = yield "A";
  console.log("第2次执行next")
  let second = yield first + "B";
  yield second + "C"
}
iterator = getIterator();
console.log(iterator.next(1));//{value: "A", done: false}
console.log(iterator.next(2));//{value: "2B", done: false}
console.log(iterator.next(3));//{value: "3C", done: true}
// 内部执行顺序大概是:
// iterator.next(1) =>  console.log("第1次执行next"); yield "A"
// iterator.next(2) =>  console.log("第2次执行next");let first = 2; yield first + "B";
// iterator.next(3) =>  console.log("第3次执行next");let second  = 3; yield second + "C"
  • 如上:第一次next方法无论如何传入啥都会被丢弃

2、抛出异常

  • 注入错误,强行阻止往下迭代:
    const getIterator = function *() {
      yield "A";
      yield "B";
      yield "C";
    }
    iterator = getIterator();
    console.log(iterator.next());//{value: "A", done: false}
    console.log(iterator.throw(new Error("Boom Sakalaka")));//Uncaught Error: Boom Sakalaka
    //以下代码不执行
    console.log(iterator.next());
    //如果此时还能拿到iterator,这时候 iterator.next() {value: undefined, done: true}
    // iterator.next() =>  yield "A"
    // iterator.throw(new Error("Boom Sakalaka")) => error,在 yield "B"之前抛出错误
    
    
  • 迭代捕获
 const getIterator = function *() {
    let message ="";
    yield "A";
    try{
        yield "B";
    }catch(e){
        message = e.toString()
    }
    yield message+"C";
  }
  iterator = getIterator();
 console.log(iterator.next());//{value: "A", done: false}
 console.log(iterator.next());//{value: "B", done: false}
 console.log(iterator.throw(new Error("Boom Sakalaka")));//{value: "Error: Boom SakalakaC", done: false}
 console.log(iterator.next());//{value: undefined, done: true}

以上例子可知:

throw也是一次迭代,也是在正常的next方法执行返回值 yield value 之前抛出错误,如果错误没有被处理则直接终止迭代。

等价于(注意throw的位置):

 const getIterator = function *() {
    let message ="";
    yield "A";
    try{
        yield "B";
        throw new Error("Boom Sakalaka");
    }catch(e){
        message = e.toString()
    }
    yield message+"C";
  }
  iterator = getIterator();
 console.log(iterator.next());//{value: "A", done: false}
 console.log(iterator.next());//{value: "B", done: false}
 console.log(iterator.next());//{value: "Error: Boom SakalakaC", done: false}
 console.log(iterator.next());//{value: undefined, done: true}

3、委托生成器

有些时候多个生成器需要合并成一个。
语法:

getIteratorFunc =  function*() {
    yield * 【新的迭代器】
} 
  • 简单委托,举个例子:

幼儿园发水果:苹果3个,香蕉2根,在两个篮子。发完了苹果,才能发香蕉。
这时候老师只能拿一个篮子,把苹果和香蕉放一个篮子,问题解决。

getAppleIterator =  function*() {
    yield "苹果1"
    yield "苹果2"
    yield "苹果3"
}
getBananaIterator = function*(){
    yield "香蕉1"
    yield "香蕉2"
}
 apple = getAppleIterator()
console.log(apple.next());//{value: "苹果1", done: false}
console.log(apple.next());//{value: "苹果2", done: false}
console.log(apple.next());//{value: "苹果3", done: false}

banana = getBananaIterator()
console.log(banana.next());//{value: "香蕉1", done: false}
console.log(banana.next());//{value: "香蕉2", done: false}

可以写成这样:

getFruitIterator =  function*(){
    yield *getAppleIterator();
    yield *getBananaIterator();
}
fruit = getFruitIterator();
console.log(fruit.next());//{value: "苹果1", done: false}
console.log(fruit.next());//{value: "苹果2", done: false}
console.log(fruit.next());//{value: "苹果3", done: false}
console.log(fruit.next());//{value: "香蕉1", done: false}
console.log(fruit.next());//{value: "香蕉2", done: false}
  • 高端委托:利用生成器返回值处理复杂任务
getAppleIterator =  function*() {
    yield "苹果1"
    yield "苹果2"
    return 1
}
getBananaIterator = function*(count){
    yield `香蕉${count}`
}
getFruitIterator =  function*(){
   const count =  yield *getAppleIterator();
   yield *getBananaIterator(count);
}
fruit = getFruitIterator();
console.log(fruit.next());//{value: "苹果1", done: false}
console.log(fruit.next());//{value: "苹果2", done: false}
console.log(fruit.next());//{value: "香蕉1", done: false}
console.log(fruit.next());//{value: undefined, done: true}
  • 可委托迭代
    字符串、数组啥的都可以迭代出来:
getIterator =  function*() {
    yield * "123"
} ;
 it =  getIterator();
console.log(it.next());//{value: "1", done: false}
console.log(it.next());//{value: "2", done: false}
console.log(it.next());//{value: "3", done: false}
console.log(it.next());//{value: undefined, done: true}
getIterator =  function*() {
    yield * [1,2,3]
} ;
 it =  getIterator();
console.log(it.next());//{value: 1, done: false}
console.log(it.next());//{value: 2, done: false}
console.log(it.next());//{value: 3, done: false}
console.log(it.next());//{value: undefined, done: true}

*五、异步任务执行

讲了那么多,还是没啥好用的。这些新特性真正存在的意义恐怕就是异步编程了。
举个ajax 请求的例子:

$.ajax({
    url:"xxxxx.do",
    success:function(value){
        console.log(value)
    }
})

如果需要嵌套,那就麻烦了:

$.ajax({
    url:"xxxxx.do",
    success:function(value){
       $.ajax({
            url:"xxxxx.do",
            success:function(value){
              $.ajax({
                url:"xxxxx.do",
                success:function(value){
                    console.log(value)
                }})
            }
        })
    }
})

根据以上的知识点:

  • yield 会暂停函数执行过程,等待下次next的迭代:

  • 在迭代器next传入参数会被下一个next执行操作捕获;

一般来说我们会想到在业务里面使用生成器,给我们可以迭代的数据。
我们亦可以反其道行之:生成器里面写业务,由一个迭代工具(任务执行工具)帮我们迭代。不关注什么时候next。

1、简单任务执行器

requestIteratorCreator = function*(){
   const result = yield 2;
   yield result
}
run = function(requestIteratorCreator){
    task = requestIteratorCreator();
	let result = task.next();
	doTask = function(){
        if(!result.done){
            result = task.next(result.value);
            doTask()
		}
	}
	doTask()
}

run(function*(){
    const result = yield 1;//
    console.log(result);//1
})

2、加入异步处理

ajaxRequest = function(url,callback){
        setTimeout(function(){
            callback(`成功返回${url}的数据`)
        },1000);
    }
//普通的异步请求:
ajaxRequest("/user/login",function(data){
    console.log(data,"登陆成功!");
    console.log("正在获取用户基础信息")
    ajaxRequest("/user/getUserInfo",function(data){
    	console.log(data,"用户基础信息获取成功!");
	});
});
//成功返回/user/login的数据 登陆成功!
//正在获取用户基础信息
//成功返回/user/getUserInfo的数据 用户基础信息获取成功!

如果是多次请求嵌套,那就是地狱回调了。我们可以利用yield 暂停这个特性,还有next传值来处理异步:

requestCreater = function(url){
   return function(callback){
       return ajaxRequest(url,callback)
   } 
}

run = function(requestIteratorCreator){
     task = requestIteratorCreator();
	 result = task.next();
     doTask = function(){
         if(!result.done){
             if(typeof result.value === 'function'){
             	result.value(function(data){
                       result = task.next(data);
                       doTask()
             	})
             }else{
                result = task.next(task.value);
                doTask()
             }
         }
     }
     doTask()
}

run(function *(){
    //写同步代码啦
    const loginResult = yield requestCreater("/user/login")
    console.log(loginResult)
    const userInfoResult = yield requestCreater("/user/userInfo")
    console.log(userInfoResult)
})
//成功返回/user/login的数据
//成功返回/user/userInfo的数据
//美滋滋啊!!!!

异步处理数据,ES6提供了更加有趣的Promise

附录:ES2018(ES9)异步迭代

上面异步处理提到的Promise,在ES7发扬光大,然后ES9加入了异步迭代的方法。
我们先来了解下:
1、async-await
ES7的async-await带我们走向光明

fetchRequest = function(url){
  return new Promise((resolve,reject) =>{
    setTimeout(function(){
      resolve(`成功返回${url}的数据`)
    },1000);
  })
}
async function runFetchDemo() {
	const loginInfo = await fetchRequest("/user/login");
	console.log(loginInfo)
	const userInfo = await fetchRequest("/user/userInfo");
	console.log(userInfo)
}
runFetchDemo();
//成功返回/user/login的数据
//成功返回/user/userInfo的数据

2、Promise 的异步迭代

getFetchIterator = function*(){
    yield fetchRequest("/user/login");
    yield fetchRequest("/user/userInfo");
}
async function runFetchDemo() {
   asyncIterator = getFetchIterator();
  const loginInfo =  await asyncIterator.next();
  console.log(loginInfo)
  const userInfo =  await asyncIterator.next();
  console.log(userInfo)
  // 瞬间输出两个 padding的Promise,很明显不是我们要的
  
  asyncIterator = getFetchIterator();
  for await (const item of asyncIterator) {
    console.log(item)
  }
  //成功返回/user/login的数据
  //成功返回/user/userInfo的数据
}

是不是觉得简化了很多?
好的我讲完了。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值