JavaScript的迭代器和生成器

107 阅读6分钟

迭代器

背景知识

1.什么是迭代?

从一个数据集合中按照一定的顺序,不断取出数据的过程

2.迭代和遍历的区别?

迭代强调的依次区数据,并不保证取多少,也不保证把所有数据取完

遍历强调的是整个数据依次全部取出

3.迭代器

对迭代过程的封装,在不同的语言中有不同的表现的形式,通常为对象

4.迭代模式

一种设计模式,用于统一迭代过程,并规范化迭代器规格:

  • 迭代器应该具有得到下一个数据的能力
  • 迭代器应该具有判断是否还有后续数据的能力

JS中的迭代器

JS规定,如果一个对象具有next方法,并且该对象返回一个对象,该对象的格式如下:

{value:值,done:是否迭代完成}

则认为该对象是一个迭代器

含义:

  • next方法用于得到下一个数据
  • 返回的对象
    • value:下一个数据的值
    • done:boolen是否迭代完成

实现一个迭代器

const arr = [1, 2, 3, 4, 5]
const iterator = {
    i: 0,
    next(){
        let result = {
            value: arr[this.i],
            done: this.i >= arr.length
        }
        this.i++
        return result
    }
}
iterator.next()//{value:1,done:false}
iterator.next()//{value:2,done:false}
iterator.next()//{value:3,done:false}
iterator.next()//{value:4,done:false}
iterator.next()//{value:5,done:false}

实现一个创建迭代器的函数

function createIterator(arr){
    let i = 0
    return {
        next(){
            let result = {
                value: arr[i],
                done: i >= arr.length
            }
            i++
            return result
        }
    }
}
const arr1 = [1, 2, 3, 4, 5]
const arr2 = [4, 5, 6, 7, 8]
const iter1 = createIterator(arr1)
const iter2 = createIterator(arr2)
iter1.next()//{value: 1, done: false}
iter2.next()//{value: 4, done: false}

实现一个斐波那契数列的迭代器

function createFeiboIterator(){
    let prev1 = 1 //当前数的前一个数
    let prev2 = 1 //当前数的前两个数
    let n = 1//第几个数
    return {
        next(){
            let value 
            if(n <= 2) {
                value = 1
            }else {
                value = prev1 + prev2
            }
            let result = {
                value,
                done: false//永远不会迭代完成
            }
            prev2 = prev1
            prev1 = result.value
            n++
            return result 
        }
    }
}
const feiboIterator = createFeiboIterator()
feiboIterator.next() //{value:1,done:false}
feiboIterator.next() //{value:1,done:false}
feiboIterator.next() //{value:2,done:false}
feiboIterator.next() //{value:3,done:false}
feiboIterator.next() //{value:5,done:false}

可迭代协议与for-of循环

  • 迭代器(iterator):一个具有next方法的对象,next方法返回下一个数据并且能指示是否迭代完成
  • 迭代器创建函数(iterator creator):一个返回迭迭代器的函数

可迭代协议

ES6规定,如果一个对象具有知名符号属性Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)

//可迭代对象
const obj = {
    [Symbol.iterator](){
        return {
            next(){
                return {value:"1",done:false}
            }
        }
    }
}
//obj就是一个可迭代对象,因为它具有知名符号属性Symbole.iterator,其属性值是一个迭代器创建函数

思考:如何知晓一个对象是否是可迭代的

//数组是一个可迭代对象
const arr = [1, 2, 3]
const iterator = arr[Symbol.iterator]()
iterator.next()//{value:1,done:false}
iterator.next()//{value:2,done:false}
iterator.next()//{value:3,done:false}

思考:如何遍历一个可迭代对象?

//遍历一个可迭代对象
const arr = [1,2,3]
const iterator = arr[Symbol.iterator]()
let result = iterator.next()
while(!result.done){
    let item = result.value
    console.log(item)
    //下一次迭代
    result = iterator.next()
}

for-of循环

for-of循环用于遍历可迭代对象,格式如下

const arr = [1,2,3]
for(let item of arr){
    console.log(item)
}
//for of 必须遍历的是可迭代对象,其就是下方遍历方式的语法糖
const iterator = arr[Symbol.iterator]()
let result = iterator.next()
while(!result.done){
    let item = result.value
    console.log(item)
    result = iterator.next()
}

只要是可迭代对象for-of都可以遍历,看下面的例子

const obj = {
    a:1,
    b:2,
    [Symbol.iterator](){
        //获取属性
        let keys = Object.keys(this)
        let i = 0
        return {
            next: () => {
                let paramName = keys[i]
                let paramValue = this[paramName]
                let result = {
                    value:{paramName,paramValue},
                    done: i >= keys.length
                }
                i++
                return result
            }
        }
    }
}
for(const item of obj){
    console.log(item)
}
//{paramName: "a", paramValue: 1}
//{paramName: "b", paramValue: 2}

展开运算符与可迭代对象

展开运算符可以作用于可迭代对象,这样就可以轻松的将可迭代对象转换为数组

const obj = {
    a:1,
    b:2,
    [Symbol.iterator](){
        //获取属性
        let keys = Object.keys(this)
        let i = 0
        return {
            next: () => {
                let paramName = keys[i]
                let paramValue = this[paramName]
                let result = {
                    value:{paramName,paramValue},
                    done: i >= keys.length
                }
                i++
                return result
            }
        }
    }
}
const arr = [...obj]
console.log(arr)
//[{paramName: "a", paramValue: 1},{paramName: "b", paramValue: 2}]

生成器(Generator)

1.什么是生成器?

生成器是一个通过构造函数Generator创建的对象,生成器即是一个迭代器,同时又是一个可迭代对象

2.如何创建生成器?

生成器的创建必须使用生成器函数(Generator Function)

3.如何书写一个生成器函数?

//这是一个生成器函数,该函数一定返回一个生成器
function *method(){
    
}
const generator = method()

4.生成器函数内部是如何执行

生成器函数内部是为了给生成器的每一次迭代提供数据,

每次调用生成器的next的方法,将导致生成器函数运行到下一个yield关键字的位置

yield是一个关键字,该关键字只能在生成器内部使用,表达“产生”一个迭代数据

function *test(){
    console.log("第一次执行")
    yield 1
    console.log("第二次执行")
    yield 2
    console.log("第三次执行")
}
const generator = test()
console.log(generator.next())
//"第一次执行" {value:1,done:false}
console.log(generator.next())
//"第二次执行" {value:2,done:false}
console.log(generator.next())
//"第三次执行" {value:undefined,done:true}

生成器简化迭代器的书写

const arr = [1,2,3]
function createIterator(arr){
    let i = 0
    return {
        next(){
            let result = {
                value:arr[i],
                done:i >= arr.length
            }
            i++
            return result
        }
    }
}
const iterator = createIterator(arr)
iterator.next()
function *createGenerator(arr){
    for(const item of arr){
        yield item
    }
}
const generator = createGenerator(arr)
generator.next()

简化斐波那契数列迭代器的案例

function createFeiboIterator(){
    let prev1 = 1
    let prev2 = 1
    let n = 1
    return {
        next(){
            let value
            if(n <= 2){
                value = 1
            }else {
                value = prev1 + prev2
            }
            let result = {
                value,
                done:false
            }
            prev2 = prev1
            prev1 = result.value
            n++
        }
    }
}
const iterator = createFeiboIterator()
iterator.next()

function *createFeiboGenerator(){
    let prev1 = 1
    let prev2 = 1
    n = 1
    while(true){
        if(n <= 2) {
            yield 1
        }else{
            let newValue = prev1 + prev2
           yield newValue
            prev2 = prev1
            prev1 = newValue
        }
        n++
    }
}
const generator = createFeiboGenerator()
generator.next()

5.有哪些需要注意的细节?

  • 1)生成器函数可以有返回值,返回值出现在第一次done为true的value属性中

  • function *test(){
        console.log("第一次执行")
        yield 1
        console.log("第二次执行")
        yield 2
        console.log("第三次执行")
        return 3
    }
    const generator = test()
    console.log(generator.next())
    //"第一次执行" {value:1,done:false}
    console.log(generator.next())
    //"第二次执行" {value:2,done:false}
    console.log(generator.next())
    //"第三次执行" {value:3,done:true}
    
  • 2)调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值

  • function* test(){
        let info = yield 1
        console.log(info)
        yield 2 + info
    }
    const generator = test()
    generator.next()
    //{value 1,done:false}
    generator.next()
    //5
    //{value 7,done:false}
    
  • 3)第一次调用next方法的时候传参没有任何意义

  • 4)在生成器函数内部可以调用其他生成器函数,但是注意要加上*号

  • function* test1(){
        yield 1
    }
    function* test(){
        yield *test1()
        yield 2
    }
    const generator = test()
    generator.next() //{value:1,done:false}
    generator.next() //{value:2,done:false}
    generator.next() //{value:undefined,done:true}
    

6.生成器的其他API

  • return方法:调用该方法可提前结束生成器函数,从而让整个迭代过程结束

  • function *test(){
        yield 1
        yield 2
        yield 3
    }
    const generator = test()
    generator.next()//{value:1,done:false}
    generator.return()//{value:undefined,done:true}
    generator.next()//{value:undefined,done:true}
    
  • throw方法:调用该方法可以在生成器中产生一个错误