Skip to content

迭代器和生成器

1.迭代器

1.1 迭代器对象

迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,设计人员使用此接口无需关心容器对象的内存分配的实现细节。其本质就是一个对象,符合迭代器协议(iterator protocol)

符合迭代器协议的对象,叫做迭代器对象

迭代器协议:

  1. 其对象包含一个next函数
  2. 调用next函数返回一个对象,其对象中包含两个属性:
    • done:布尔值,指示当前迭代是否完成
      • 如果这个迭代器没有迭代完成即返回{done:false}
      • 当这个迭代器完成了即返回{done:true}
    • value:(值),返回当前迭代后的结果,它可以返回js中的任何值,TS中表示可为:value:any类型

以下是一个基本的迭代器

js
const arr = ['11', '22', '33']
let index = 0
const iterator = {
    next() {
        return {
            value: index > arr.length ? undefined : arr[index++],
            done: index > arr.length ? true : false,
        }
    },
}

console.log(iterator.next()) // {value: '11', done: false}
console.log(iterator.next()) // {value: '22', done: false}
console.log(iterator.next()) // {value: '33', done: false}
console.log(iterator.next()) // {value: undefined, done: true}

以上代码符合迭代器的特点:是一个对象,包含next()函数且返回值为一个对象包含两个属性

但缺使用了全局变量,而且迭代器对象的源对象单一,只能应用于这一个数组

我们需要做如下封装

js
const arr = ['11', '22', '33']

const createIterator = arr => {
    let index = 0
    return {
        next() {
            return {
                value: index > arr.length ? undefined : arr[index++],
                done: index > arr.length ? true : false,
            }
        },
    }
}

const it = createIterator(arr)

console.log(it.next()) // {value: '11', done: false}
console.log(it.next()) // {value: '22', done: false}
console.log(it.next()) // {value: '33', done: false}
console.log(it.next()) // {value: undefined, done: true}

效果和第一个示例一样,但封装成了函数,可以创建多个迭代器对象,并且应用与多个数组,提升了可复用性

1.2 可迭代对象

迭代器对象和可迭代对象并不是一个东西,虽然它们存在关联

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

  • 包含一个属性名为[Symbol.iterator]的方法,且这个方法返回了一个迭代器对象

使用for...of遍历的时候,本质就是调用的这个函数,也就是[Symbol.iterator]为key的方法

1.2.1 JS内置可迭代对象

  1. String
  2. Array
  3. Set
  4. NodeList 类数组对象
  5. Arguments 类数组对象
  6. Map

控制台打印这些对象的原型可以发现一个Symbol(Symbol.iterator)方法,我们也可以直接使用

js
const arr = ['11', '22', '33']

const it = arr[Symbol.iterator]()

console.log(it.next()) // {value: '11', done: false}
console.log(it.next()) // {value: '22', done: false}
console.log(it.next()) // {value: '33', done: false}
console.log(it.next()) // {value: undefined, done: true}

使用map的迭代器

js
const map = new Map([
    ['a', 111],
    ['b', 222],
    ['c', 333],
])

const it = map[Symbol.iterator]()
console.log(it.next()) // {value: ['a', 111], done: false}
console.log(it.next()) // {value: ['b', 222], done: false}
console.log(it.next()) // {value: ['c', 333], done: false}
console.log(it.next()) // {value: undefined, done: true}

1.2.2 实现可迭代对象

js中对象(object)是没有迭代器的,所以不可迭代,但我们可以自己实现

js
const obj = {
    a: 111,
    b: 222,
    c: 333,
    [Symbol.iterator]() {
        const arr = Object.keys(this)
        let index = 0
        const that = this
        return {
            next() {
                return {
                    value: index > arr.length ? undefined : that[arr[index++]],
                    done: index > arr.length ? true : false,
                }
            },
        }
    },
}

// 使用迭代器对象
const it = obj[Symbol.iterator]()
console.log(it.next()) // {value: 111, done: false}
console.log(it.next()) // {value: 222, done: false}
console.log(it.next()) // {value: 333, done: false}
console.log(it.next()) // {value: undefined, done: true}

// 使用数组结构
const [a, b, c] = obj
console.log(a, b, c)

// 使用for..of循环
for (const key of obj) {
    console.log(key)
}

以上代码通过Object.keys()把对象的键存在数组中,每次迭代通过index获取数组中的键,从而获取obj的值

注意:next()函数中的this需要单独存一份,或者把next写成箭头函数

以上写法可以简化为以下写法

js
const obj = {
    a: 111,
    b: 222,
    c: 333,
    [Symbol.iterator]() {
        return Object.values(this)[Symbol.iterator]()
    },
}

// 使用迭代器对象
const it = obj[Symbol.iterator]()
console.log(it.next()) // {value: 111, done: false}
console.log(it.next()) // {value: 222, done: false}
console.log(it.next()) // {value: 333, done: false}
console.log(it.next()) // {value: undefined, done: true}

// 使用数组结构
const [a, b, c] = obj
console.log(a, b, c)

// 使用for..of循环
for (const key of obj) {
    console.log(key)
}

Object.values(this)返回一个包含对象所有值的数组,那么这个数组中已经有一个[Symbol.iterator]()函数,可以直接调用

2.生成器

生成器是ES6新增的一种可以对函数控制的方案,能灵活的控制函数的暂停执行,继续执行等。

生成器函数和普通函数的不同

  • 定义: 普通函数function定义,生成器函数function*,要在后面加*
  • 生成器函数可以通过 yield 来控制函数的执行
  • 生成器函数返回一个生成器(generator),生成器是一个特殊的迭代器

2.1 基本使用

生成器又叫做generator函数,是一种特殊类型的函数,generator函数使用function*修饰

js
function *fn(){

}

TIP

箭头函数不能作为生成器

2.2 yield关键字

在生成器函数中可通过yield关键字返回一个值

生成器函数返回一个生成器对象,使用该对象的next()函数可以逐步获取 yield 的值。

js
function* fn() {
    yield 1
    yield 2
}
const suspend = fn()

console.log(suspend.next()) // {value: 1, done: false}
console.log(suspend.next()) // {value: 2, done: false}
console.log(suspend.next()) // {value: undefined, done: true}

next()函数返回的对象有两个属性

  • value:每次yield返回的值,若已经没有yield的返回值,则为undefined
  • done:布尔类型,指示迭代是否完成

TIP

若迭代没有完成,每次调用next()函数都会暂停生成器的执行,再次调用next(),才会继续执行

2.3 yield*委托

yield* 是在生成器函数中使用的一种特殊语法,用于委托给另一个可迭代对象或生成器。使用 yield* 可以方便地从另一个生成器或可迭代对象中传递值,而不需要手动迭代它们。yield* 的基本语法如下:

js
function* generatorFunction() {
    yield* anotherGenerator();
}

在这个示例中,generatorFunction 会委托给 anotherGenerator(),并将其产生的所有值“传递”到调用 generatorFunction 的上下文中。

js
function* innerGenerator() {
    yield 1;
    yield 2;
}

function* outerGenerator() {
    yield* innerGenerator(); // 委托给 innerGenerator
    yield 3;
    yield 4;
}

const gen = outerGenerator();

console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().value); // 4
console.log(gen.next().value); // undefined

在这个示例中,outerGenerator 使用 yield* 委托给 innerGenerator,因此在调用 outerGenerator 时,第一次返回的是 12,随后返回 34

yield*可以 委托可迭代对象

js
function* arrayGenerator() {
    yield* [111,]; // 委托给数组
}

const it = arrayGenerator();

console.log(it.next()) // {value: 111, done: false}
console.log(it.next()) // {value: 222, done: false}
console.log(it.next()) // {value: 333, done: false}
console.log(it.next()) // {value: undefined, done: true}

2.4 生成器特性

生成器特性有如下特性

  • 可暂停的执行:在调用 yield 时,生成器会暂停,后续的调用会从暂停的地方恢复。

  • 可以传递值:可以在 next() 方法中传递参数,这个值会成为 yield 表达式的返回值。例如:

    js
    function* generator() {
        const value1 = yield '请给我一个值'
        console.log(value1) // 输出传入的值
    }
    
    const gen = generator()
    console.log(gen.next().value) // 请给我一个值
    gen.next(42) // 输出: 42
  • 完结状态:当生成器没有更多值可返回时,done 属性为 true,且 valueundefined

MIT Licensed