迭代器和生成器

143 阅读6分钟

一、什么是迭代器

迭代器,是确使用户可在容器对象上遍历的对象,使用该接口无需关心对象的内部实现细节。迭代器本身就是一个对象,而这个对象可以帮我们对某个数据结构进行遍历的对象。其本质就是一个对象,符合迭代器协议(iterator protocol),在js中这个标准就是一个特定的next方法。说人话就是必须要有一个next函数。而next函数返回包含done和value两个属性的一个对象。

迭代器协议(iterator protocol):

  1. 调用next函数返回一个对象,其对象中包含两个属性

  2. done(完成):它的值为布尔类型,也就是true/false。如果这个迭代器没有迭代完成即返回{done:false}, 当这个迭代器完成了即返回{done:true}

  3. value(值):它可以返回js中的任何值。

1、迭代器对象:

const names = ['wbb', 'lbb', 'fbb']
let index = 0
// 迭代器对象
const namesIterator = {
    next: function() {
        if(index < names.length) {
            return {done: false, value: names[index++]}
        } else {
            return {done: true, value: undefined}
        }
    }
}

console.log(namesIterator.next()) // { done: false, value: 'wbb' }
console.log(namesIterator.next()) // { done: false, value: 'lbb' }
console.log(namesIterator.next()) // { done: false, value: 'fbb' }
console.log(namesIterator.next()) // { done: true, value: undefined }

以上代码在对象中存在next函数,且返回了done和value两个属性,因为是一个迭代器对象。但是我们可以考虑一下,如果我们需要有多个迭代器对象呢?所以我们就需要封装一个生成迭代器对象的函数,减少冗余。

function createArrayIterator(arr) {
    let index = 0
    return {
        next: function() {
            if(index < arr.length) {
                return {done: false, value: arr[index++]}
            } else {
                return {done: true, value: undefined}
            }
        }
    }
}
const names = ['wbb', 'lbb', 'fbb']
const nameIterator = createArrayIterator(names)
console.log(namesIterator.next()) // { done: false, value: 'wbb' }
console.log(namesIterator.next()) // { done: false, value: 'lbb' }
console.log(namesIterator.next()) // { done: false, value: 'fbb' }
console.log(namesIterator.next()) // { done: true, value: undefined }

总结:

  1. 迭代器身是一个容器对象,可以帮助我们对某个数据结构进行遍历;
  2. 而这个对象需要符合迭代器协议, 在js中这个标准就是一个特定的next方法;
  3. next是 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象;
  4. done(boolean),为false时,value有值,可继续遍历,为true时,value可省略,代表遍历结束。

2、可迭代对象

首先就是一个对象,且符合可迭代对象协议(iterable protocol)

实现了[Symbol.iterator]为key的方法,且这个方法返回了一个迭代器对象

const iterableObj = {
    names: ['abc', 'zxc', 'qwe'],
    [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...of可以遍历的东西必须是一个可迭代对象,for...of本质就是使用的迭代器。

对象不是一个可迭代对象

const obj = {
    name: 'wbb',
    age: 18,
    sex: 'female'
}
// 报错了
for(let value of obj) {
    console.log(value)  // Uncaught TypeError: obj is not iterable
}

数组本身就是一个可迭代对象

const names = ['wbb', 'lbb', 'fbb']
for(let value of names) {
    console.log(value) // 'wbb' 'lbb' 'fbb'
}


const animals = ['spider', 'panda', 'bear']
//可迭代对象上有Symbol.iterator方法,并且返回一个迭代器对象
const iter = animals[Symbol.iterator]()
// 迭代器对象上有next方法
console.log(iter.next())  // { value: 'spider', done: false }
console.log(iter.next())  // { value: 'panda', done: false }
console.log(iter.next())  // { value: 'bear', done: false }
console.log(iter.next())  // { value: undefined, done: true }

3、原生可迭代对象(JS内置):

String

Array

Set

NodeList 类数组对象

Arguments 类数组对象

Map

4、为对象添加可迭代方法

const obj = {
    name: 'wbb',
    age: 18,
    sex: 'female'
}

const obj2 = {
    name: 'fbb',
    age: 32,
    sex: 'male'
}

// 1、在对象原型添加[Symbol.iterator]的方法
Object.prototype[Symbol.iterator] =  function() {
    let keys = []
    let index = 0
    for(let key in this) {
        keys.push(key)
    }

    // 2、返回一个迭代器对象
    return {
        // 这里必须要用箭头函数,不然其this的指向是不明确的。
        // (ps: 若有兴趣,可一键三连,后面再写一篇关于this指向问题的文章)
        next: () => {
            if(index > keys.length) {
                return { done: true, value: undefined }
            } else {
                let result = { done: false, value: this[keys[index]] }
                index ++
                return result
            }
        }
    }
}

for(let key of obj) {
    console.log(key)
}
// 输出
// wbb、18、female、undefined

for(let key of obj2) {
    console.log(key)
}

// 输出
// fbb、32、male、undefined

二、什么是生成器

它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等

平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

function foo() {
  const value1 = 100
  console.log(value1)

  const value2 = 200
  console.log(value2)

  const value3 = 300
  console.log(value3)
}
foo()

// 比如以上我想执行到200就暂停呢,这时候就可以使用生成器函数,对函数进行精准的控制

生成器函数也是一个函数,但是和普通的函数有一些区别:

  1. 生成器函数需要在function的后面加一个符号:*
  2. 生成器函数可以通过yield关键字来控制函数的执行流程
  3. 生成器函数的返回值是一个Generator(生成器)

生成器事实上是一种特殊的迭代器: MDN:Instead, they return a special type of iterator, called a Generator

1、生成器的使用

function* foo() {
    // 第一段代码:
    console.log('函数开始执行')
    const value1 = 100
    console.log(value1)
    yield
    
    // 第二段代码:
    const value2 = 200
    console.log(value2)
    // return "123" // 如果有return,执行第二个next就停止结束了, {done: true, value: 123}
    yield value2
    
    // 第三段代码:
    console.log('函数执行结束')
}
  
// foo() //一行代码都不会执行
// 调用生成器函数时, 会给我们返回一个生成器对象,是一个特殊的迭代器
const generator = foo()

// 开始执行第一段代码
generator.next() 
// 输出:
// '函数开始执行',
// '100'
  
// 开始执行第二段代码
console.log("第二段代码:", generator.next()) // 有返回值,是一个对象。输出:'第二段代码: { value: 200, done: false }',由此可见生成器是一个特殊的迭代器
// 输出:
// 200

// 开始执行第三段代码
console.log("第三段代码:", generator.next()) // 第三段代码: { value: undefined, done: true }, 结束后默认返回一个状态为true,value值为undefined的迭代器对象
// 输出:
// 函数执行结束

generator.next() // 什么都没输出

// 技巧: 一个yield对应一个next

2、给生成器传值

赋值是给前一个yield的左边赋值

function* foo(num) {
    console.log("函数开始执行~")

    const value1 = 100 * num  // 第一个一般很少传
    const n = yield value1  // 这个n是4

    const value2 = 200 * n
    const count = yield value2 // 这个count是5

    const value3 = 300 * count
    const count1 = yield value3 // 这个num是6

    console.log("函数执行结束~", count1) // 输出'函数执行结束~,num'
    return "123"
}

const generator = foo(2) // 在这里可以传第一个
console.log(generator.next(3))  // 这里传是没用的,因为没有上一个yield了,拿不到的, generator.next()的返回值是{value: 200, done: false}
console.log(generator.next(4))  // generator.next()的返回值是{value: 800, done: false}
console.log(generator.next(5))  // generator.next()的返回值是{value: 1500, done: false}
console.log(generator.next(6))  // generator.next()的返回值是{value: '123', done: true}
console.log(generator.next())   // generator.next()的返回值是{value: undefined, done: true}

3、生成器替代迭代器

function* createArrayIterator(arr) {
    // 第一种写法
    // yield 'abc'
    // yield 'wbb'
    // yield 'fbb'

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

    // 第三种写法 yield* 后面 跟的是可迭代对象
    yield* arr
}
const names = ['abc', 'wbb', 'fbb']
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())

// 依次输出:
// { value: 'abc', done: false }
// { value: 'wbb', done: false }
// { value: 'fbb', done: false }
// { value: undefined, done: true }

总结:

  1. 可以控制函数的暂停执行和继续执行
  2. 通过function* bar() {} 这种形式定义
  3. 不会立马执行,而是返回一个生成器,生成器是一个特殊的迭代器对象
  4. yield 关键字可以控制函数分段执行
  5. 调用返回生成器的next方法进行执行