生成器-迭代器

334 阅读5分钟

迭代器(Iterator)

什么是迭代器?

  • 迭代器是帮助我们对某个数据结构进行遍历的对象,迭代器是一个对象
  • 这个对象需要符合迭代器协议(Iterator Protocol)
    • 迭代器协议定义了产生一系列值(无论是有限个还是无线个) 的标准方法。
    • 在js中这个标准就是一个特定的next方法
  • next方法有以下要求:
    • 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象
    • done(boolean类型)
      • 如果迭代器可以产生序列中的下一个值,则为false(等价于没有指定done的这个属性)。
      • 如果迭代器已将序列迭代完毕,则为true。这种情况下,value是可选的,如果value依然存在,即为迭代结束之后的默认返回值
    • value:迭代器返回的任何JavaScript代码done为true时可以省略

迭代器代码

function createIterator(arr) {
    const length = arr.length
    let index = 0
    
    return {
        next() {
            return { done: index >= length, value: arr[index++] }
        }
    }
}
const arr = ['aaa', 'bbb', 'ccc']

// 这里得到的iterator就是一个迭代器,因为createIterator函数返回的对象满足迭代器协议。
const iterator = createIterator(arr)
console.log(iterator.next());   // { done: false, value: 'aaa' }
console.log(iterator.next());   // { done: false, value: 'bbb' }
console.log(iterator.next());   // { done: false, value: 'ccc' }
console.log(iterator.next());   // { done: true, value: undefined }

什么是可迭代对象

  • 当一个对象实现了可迭代协议(iterable protocol) 时,它就是一个可迭代对象
  • 这个对象要求是必须实现@@iterator方法,在代码中使用Symbol.iterator访问该属性。
  • String,Array,Map,Set,arguments对象,NodeList集合等都是可迭代对象。可以通过调用Symbol.iterator生成一个迭代器对象

可迭代对象的好处?

当一个对象变成可迭代对象,进行某些迭代操作时,比如for...of操作时,其实就会使用到它的**@@iterator方法**。

可迭代对象的应用

  • JavaScript语法中:for..of,展开语法(spread syntax),yield,解构赋值。
  • 创建对象时:new Set(iterable), new Map(iterable), new WeakMap(iterable), new WeakSet(iterable)等等。
  • 一些方法的调用:Promise.all(itrable), Promise.race(iterable), Array.from(iterable)等等。

可迭代对象代码

const obj = {
    val: ['aaa', 'bbb', 'ccc'],
    [Symbol.iterator]() {
        let index = 0
        return {
        // 这里的next用到箭头函数,是为了能够让this指向obj这个对象。
            next: () => {
                return { 
                    done: this.val.length <= index,
                    value: this.val[index++]
                }
            }
        }
    }
}

for (const i of obj) {
    console.log(i) // 打印结果分别是 aaa, bbb, ccc
}

迭代器中断

  • 迭代器在某些情况下会在没有完全迭代的情况下中断
    • 比如遍历过程中通过break,continue,return,throw中断的循环操作。
    • 比如在解构的时候,没有解构所有的值。
  • 如果想要监听中断的话,可以添加return方法。return方法需要返回一个对象,如果没有返回一个对象会报错
const obj = {
    val: ['aaa', 'bbb', 'ccc'],
    [Symbol.iterator]() {
        let index = 0
        return {
            next: () => {
                return {
                    done: this.val.length <= index,
                    value: this.val[index++]
                }
            },
            return: () => {
                // 当迭代器中断时,会来到该函数
                // 必须返回一个对象
                return {}
            }
        }
    }
}

生成器

什么是生成器?

  • 生成器是ES6新增的一种函数控制,使用的方案。它可以让我们更加灵活的控制函数什么时候继续执行,暂停执行等
  • 生成器函数也是一个函数,但是和普通函数有一些区别:
    • 生成器函数需要在function的后面加上一个符号 *
    • 生成器函数可以通过yield关键字来控制函数的执行流程。
    • 生成器函数的返回值是一个Generator(生成器)生成器实际上是一种特殊的迭代器

生成器函数代码

function* foo() {   // 这里函数定义是有 * 的
    console.log(1)
    yield 1   // 这里yield后面的值会被作为返回的迭代器对象的value值
    console.log(2)
    yield 2
    console.log(3)
}

const generator = foo()  // generator就是一个生成器
// 执行到第一个yield并且暂停
console.log(generator.next()) // 1 { value: 1, done: false }
// 执行到第二个yield并且暂停
console.log(generator.next()) // 2 { value: 2, done: false }
// 执行剩余代码
console.log(generator.next()) // 3 { value: undefined, done: true }
  • 在执行foo函数时,会发现函数体根本没有执行,它只是返回了一个生成器对象
  • 如果想要执行函数体里面的代码,调用next即可
  • 如果不希望next返回的迭代器对象的value的值为undefined,可以通过yield来返回结果

生成器函数传递参数

  • 既然生成器可以暂停来分段执行,我们可以给每个分段来传递参数
  • 我们在调用next函数时,可以给next函数传递参数,那么这个参数会作为上一个yield语句的返回值
  • 一般情况下,不会给第一个next传递参数,因为第一个next前面没有yield
function* bar() {
    const num1 = yield 'a'
    const num2 = yield num1
    const num3 = yield num2
}

const generator = bar()
console.log(generator.next()) // { value: 'a', done: false }
console.log(generator.next('b')) // { value: 'b', done: false }
console.log(generator.next('c')) // { value: 'c', done: false }
console.log(generator.next()) // { value: undefined, done: true }

提前结束生成器函数-return函数

  • return函数也可以给生成器函数传递参数。不过执行return函数后,生成器函数就会结束,之后调用next也不会继续生成值了。当调用return时,value为return传入的参数。、
function* bax() {
    const val1 = yield 'a'
    console.log('val1', val1) // 这里是不会被执行的,因为调用了return函数
}
const generator = bax()
console.log(generator.next()) // { value: 'a', done: false }
console.log(generator.return(1)) // { value: 1, done: true }
console.log(generator.next()) // { value: undefined, done: true }

生成器抛出异常-throw函数

  • 除了可以给生成器函数内部传递参数之外,也可以该生成器行内部抛出异常
  • 抛出异常后,可以在生成器函数中捕获异常
function* baz() {
    console.log('start')
    
    try {
        yield 1
    } catch (err) {
        console.log('生成器函数内部捕获异常', err) // 这里会捕获到 'err message'
        yield 'catch内部使用yield' 
    }
    yield 'catch外部使用yield'
}

const generator = baz()
console.log(generator.next()) // { value: 1, done: false }
console.log(generator.throw('err message'))  // { value: 'catch内部使用yield', done: false }
console.log(generator.next()) // { value: 'catch外部使用yield', done: false }

生成器替代迭代器

  • 生成器是一种特殊的迭代器,在某些情况下可以使用生成器替代迭代器
// 实现让对象能够使用for...of遍历
function* generator() {
    const keys = Object.keys(this)
    
    for (let i = 0; i < keys.length; i++) {
        yield this[keys[i]]
    }
}

const obj = {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    [Symbol.iterator]: generator
}

for (const item of obj) {
    console.log(item)  // 1, 2, 3, 4
}