ES6的生成器Generator和迭代器Iterator

正题如下:

问:有没有遇到有人问你对象为什么不能用for … of … 遍历?

答:没错,我遇到了,虽然我回答不能,但是发现通过某些修改后还是可以使用的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有没有发现对象原型并没有那个可遍历的方法

所以对象不能使用for of 去遍历的原因:对象不是可迭代的

但是对ES6有过了解的,都知道一个函数,那就是生成器和迭代器,那具体是干什么的?

首先我先说下如何 让对象可以使用 for ... of ..来遍历出 key 值


    let obj = {
      name: "fqniu",
      age: 25,
      gender: "boy",
      from: "china"
    }

    // for(var val of obj){
    //   console.log(val)
    // }
    // 会直接报错:obj is not iterable
    
    // 先创建一个生成器 create 注意返回一个迭代器
    function* create(o) {
      for (var key in o) {
        yield o[key]
      }
    }
    
    Object.defineProperty(obj, Symbol.iterator, {
      value(){
        return create(obj)
      }
    })

    for(var val of obj){
      console.log(val)
    }
    // 打印出key值
    

在这里插入图片描述

   let obj = {
      name: "fqniu",
      age: 25,
      gender: "boy",
      from: "china"
    }

    // for(var val of obj){
    //   console.log(val)
    // }
    // // 报错:obj is not iterable

    // function* create(o) {
    //   for (var key in o) {
    //     yield o[key]
    //   }
    // }

    // 或者:
    function* create(o) {
      let idx = 0
      let length = Object.keys(o).length
      for (var key in o) {
        idx++
        yield { value: o[key], done: idx === length }
      }
    }
    Object.defineProperty(obj, Symbol.iterator, {
      value() {
        return create(obj)
      }
    })

    for (var val of obj) {
      console.log(val) // 打印如下
    }

在这里插入图片描述

以下引用 阮一峰的ES6

Generator 函数是 ES6 提供的一种异步编程解决方案;

  • generator 生成器函数,返回一个迭代器(遍历器可以依次遍历generator函数内部的每一个状态)
  • yield 暂停 / return 结束

Iterator 是迭代器 (遍历)

  • next() 返回格式 {value,done} 的对象,value:迭代到当前位置的值,done:迭代器是否迭代完毕;
  • for… of … 内部会自动调用 迭代器的 next()方法;就是可以自动遍历generator函数运行时生成的 Iterator对象,且此时不再需要调用next方法;
    function* show(){
        console.log('start')
        yield 10
        console.log(1)
        yield 20
        console.log(2)
        return 100
        console.log('end')
    }

在这里插入图片描述

上面代码定义了一个 Generator函数show,它内部有两个yield表达式(hello和world),即该函数有四个状态:10,20,100 和 return语句(结束执行)。

然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

yield表达式

由于generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供一种暂停执行的函数,yield表达式就是暂停标志;
遍历器对象的next方法的运行逻辑如下:

  1. 遇到yield表达式,就暂停执行后面的操作,并将近跟着yield后面的那个表达式的值,作为返回的对象的value属性值
  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
  3. 如果没有再遇到yield表达式,就一直运行到函数结束,直到return 语句为止,并将return 语句后面的表达式的值,作为返回的对象的value属性值
  4. 如果该函数没有return 语句,则返回的对象value属性值为undefined

next方法的参数

yield表达式本身没有返回值,或者说总是返回undefined,next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值;

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
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 }

在这里插入图片描述注意:yield本身没有返回值,也就是说 var y = 2 * (yield (5+1)) ,
var y = 2 * (yield 6) 而yield 6 没有返回值,或者说返回undefined ,
所以导致 y的值 等于 2 * undefined 即是NaN

在这里插入图片描述

引用:
上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5+ NaN + undefined,即NaN。

如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y /3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。

注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

迭代器Iterator

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

Iterator 的遍历过程是这样的。

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

如果想要深入可以参考阮一峰的ECMAScript 6 入门
阮一峰的ECMAScript 6 入门 的Iterator 和for … of 循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值