likes
comments
collection
share

迭代器Iterator和生成器Generator

作者站长头像
站长
· 阅读数 57

迭代器Iterator

迭代器(iterator)在不同编程语言当中的实现方式是不太一样的,本文主要介绍JavaScript当中的迭代器实现方式。在JavaScript当中,迭代器是一个具体的对象,但这个对象不是普通定义的对象,需要符合JavaScript中对于迭代器协议(iterator protocol)实现的标准

  • 该对象需要定义一个next方法
  • next方法需要返回一个对象,该对象包含两个参数done和value
    • done:布尔值,表示该迭代对象是否迭代完成,如果还能继续迭代下去则done为false,否则为true
    • value:迭代对象中获取到的具体值,通常done为true迭代完成后可以省略,对应的value就是undefined

实现简单的迭代器

如果光看上述对于迭代器的描述,其实并不好理解迭代器到底是什么东西。我们可以自己设计一个简单的迭代器来体验一下

const arr = ['aaa', 'bbb', 'ccc', 'ddd']
    
// 给数组创建其对应的迭代器arrIterator
let index = 0
const arrIterator = { 
  next: function() {
    // 返回一个对象,包含done和value属性
    if (index < arr.length) {
      return {done: false, value: arr[index++]}
    }else {
      // value属性可省略,默认返回undefined
      return {done: true}
    }
  },
}

arrIterator迭代器首先是一个对象,并且里面定义了next方法,里面的return返回的也是对象,包含done和value属性,但是并不是说直接返回对象,而是会配合index进行判断返回不同的结果。 随后我们多次调用arrIterator中的next方法,看看结果如何

console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())

迭代器Iterator和生成器Generator

接下来尝试去迭代对象中的每个value,设计一个对象的迭代器

const obj = {
    name: 'zs',
    age: 18,
    address: 'china'
}
// 定义关于对象的迭代器
let index = 0
const objIterator = {
    next: function() {
        // 获取对象obj的所有key
        const keys = Object.keys(obj)
        if (index < keys.length) {
            return { done: false, value: obj[keys[index++]] }
        } else {
            return { done: true }
        }
    }
}
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())

迭代器Iterator和生成器Generator

可迭代对象

虽然上面我们实现了对数组以及对象的迭代器,但是会发现要迭代的对象和迭代器似乎割裂开来,那么是否可以将迭代器定义到数组和对象里面呢?当然可以,我们把迭代器的实现逻辑放到对象当中试试。

const obj = {
    name: 'zs',
    age: 18,
    address: 'china',
    // 我们在obj里面定义一个名为Symbol.iterator的方法,把上述代码搬到该方法当中并返回一个迭代器
    [Symbol.iterator]: function() {
        let index = 0
        const objIterator = {
            next: function() {
                const keys = Object.keys(obj)
                if (index < keys.length) {
                    return { done: false, value: obj[keys[index++]] }
                } else {
                    return { done: true }
                }
            }
        }
        return objIterator
   }
}
// 获取迭代器objIterator
const objIterator = obj[Symbol.iterator]()
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())

这个相当于将迭代器定义到了obj当中,属性名为[Symbol.iterator],是一个返回迭代器的函数。那么可以称这个对象为可迭代对象。需要注意的是平常定义的对象都是不可迭代的,即它不能通过for...of..方法进行迭代,而字符串,数组,arguments都可以通过for...of...迭代,其本质都是因为它们内部都定义了一个属性名为[Symbol.iterator]的方法,返回对应的迭代器,所以字符串,数组,arguments都称为是可迭代对象。

// 数组是可迭代对象,符合上述特点
const students = ['zs', 'ls', 'ww']
const arrIterator = students[Symbol.iterator]()
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())

// 可以通过for...of遍历
for (const stu of students) {
    console.log(stu)
}
console.log('----------')
// 上述obj我们也在内部定义了迭代器,也可以通过for...of遍历
for (const value of obj) {
    console.log(value)
}

迭代器Iterator和生成器Generator

因此要想使某个对象变成可迭代对象,则需要在其内部定义[Symbol.iterator]来实现迭代过程。以上便是迭代器的大概了解。但是定义一个迭代器需要定义那么多的步骤,而生成器generator则可以减少这部分定义的步骤。下面我们来介绍生成器。

生成器Generator

生成器Generator的作用是可以灵活地控制函数什么时候暂停,什么时候执行

在介绍生成器前,需要介绍生成器函数,生成器函数正如其名本质上也是一个函数,但是它与平常定义的function是有些许区别。

  • 生成器函数需要在function后面加上*号
  • 生成器函数会自动返回一个生成器generator
  • 可以在生成器函数当中使用yield关键字

我们以下面代码为示例演示生成器函数:

// 定义生成器函数,在function后面加上*号
function* foo() {
    console.log('1')
    console.log('2')
    console.log('3')
}
const generator = foo() // 不会打印1,2,3,只会返回一个生成器generator

注意:generator是一个特殊的迭代器iterator,这也就说明了generator也可以调用next方法

console.log(generator.next())

迭代器Iterator和生成器Generator

generator.next()方法是执行生成器函数内部的代码,而打印generator.next()则是获取到包含done和value属性的对象结果,可以看到done为true,value为undefined,说明该生成器函数已经执行完毕。(其实这一行相当于函数的return结果,return默认返回的值就是undefined)

那么如果在生成器函数定义yield关键字,有什么现象发生么?

function* foo() {
    console.log('1')
    console.log('2')
    yield
    console.log('3')
    console.log('4')
    yield
    console.log('5')
    console.log('6')
}
const generator = foo() 
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

迭代器Iterator和生成器Generator

所以yield关键字的作用相当于一个暂停点,控制函数的执行过程。每次遇到yield则暂停执行,返回done为false的结果。

生成器函数的参数以及返回值

上面返回的结果发现value都是undefined,那么这个value到底由谁决定呢?是由yield或者return后面的部分来作为value值。

function* foo() {
    console.log('1')
    console.log('2')
    yield '第一个next的value'
    console.log('3')
    console.log('4')
    yield '第二个next的value'
    console.log('5')
    console.log('6')
    return '第三个next的value'
}
const generator = foo() 
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

迭代器Iterator和生成器Generator

如果我想在每次执行next的时候传入参数呢,则只需要在next()中传入参数,但是接收的地方需要放到yield的前面进行赋值

function* foo(value) {
    console.log('1', value)
    console.log('2', value)
    const value1 = yield '第一个next的value'
    console.log('3', value1)
    console.log('4', value1)
    const value2 = yield '第二个next的value'
    console.log('5', value2)
    console.log('6', value2)
    return '第三个next的value'
}
const generator = foo('第一个next传的value') 
// 注意:由于第一个next前面部分没有yield关键字进行接收,所以不再第一个next传参,而是在调用函数的时候传参
console.log(generator.next())
console.log(generator.next('第二个next传的value'))
console.log(generator.next('第三个next传的value'))

迭代器Iterator和生成器Generator

生成器替代迭代器

因为生成器本身就是一种特殊的迭代器,所以接下来我们使用生成器来替代迭代器

const arr = ['aaa', 'bbb', 'ccc']

// 通过生成器来替代迭代器
function* createArrayIterator(arr) {
  for (let i = 0; i < arr.length; i++) {
    // 相当于每次调用next就暂停,arr[i]作为value的值
    yield arr[i]
  }
}

// 返回生成器,特殊的迭代器
const arrIterator = createArrayIterator(arr)
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())

迭代器Iterator和生成器Generator

后面官方还提供了关于yield的语法糖,只需要yield* 可迭代对象就可以实现for循环的效果

// 通过生成器来替代迭代器
function* createArrayIterator(arr) {
  yield* arr
}

以上就是关于迭代器iterator和生成器generator的主要介绍

转载自:https://juejin.cn/post/7245541931752947770
评论
请登录