原来是【迭代器】和【生成器】哇!!!

193 阅读5分钟

迭代器

迭代器是什么?

  • 迭代器(Iterator)是一种为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

什么是可迭代对象?

  1. 实现了正式Iterable接口

  2. 可以通过迭代器Iterator消费

实现了Iterator的内置类型:

  • 字符串
  • 数组
  • 映射-Map()
  • 集合-Set()
  • arguments对象
  • NodeList等DOM集合类型

使用迭代器实现功能的方法:

  • for-of循环
  • 数组解构
  • 扩展操作符
  • 使用Array.from()创建数组
  • 创建映射
  • 创建集合
  • promise.race()接收由期约组成的可迭代对象
  • promise.all()接收由期约组成的可迭代对象
  • yield*操作符在生成器中使用

迭代器的作用

  1. 为各种数据结构,提供一个统一的、简便的访问接口

  2. 使数据结构的成员能够按某种次序排列

  3. 实现Iterator接口提供给for...of消费

迭代器的简单使用

  • 使用Symbol.iterator获取迭代器
  • 使用next()返回迭代器对象
//使用数组演示---数组是可迭代对象
let niter = [1,2,3]

//迭代器工厂函数
//每个可迭代对象使用[Symbol.iterator]都能够找到与之绑定的迭代器工厂函数
console.log(niter[Symbol.iterator])

//执行迭代器工厂函数---返回一个迭代器
let iter = niter[Symbol.iterator]()

//使用.next()对迭代器进行操作
//next()返回迭代器对象---包含拉钩属性:done和value
//  done---布尔值,表示是否可以再次调用next()取得下一个值,当done为true时已无值可取
console.log(iter.next())//{ value: 1, done: false }
console.log(iter.next())//{ value: 2, done: false }
console.log(iter.next())//{ value: 3, done: false }
console.log(iter.next())//{ value: undefined, done: true }

迭代器是一个一次性使用的对象,在需要使用时调用相应的迭代器工厂函数产生。

如果迭代器在迭代期间被修改,则迭代器也会反应相应的变化

迭代器维护着一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象。

自定义迭代器

思路

使用实现,在类中创建一个迭代器工厂函数,被调用时将next方法返回,并使用闭包记录相应的值。

实现

class Counter {
    //size为最大值
    constructor(size){
        this.size = size
    }

    //迭代器工厂函数
    [Symbol.iterator](){
        let value = 1
        let size = this.size
        return {
            next(){//实现自定义next()功能---使用了闭包
                if(value <= size){
                    return {value:value++,done:false}
                }else{
                    return {value:undefined,done:true}
                }
            }
        }
    }
}

let test = new Counter(2)

let testIter =  test[Symbol.iterator]()

console.log(testIter.next())
console.log(testIter.next())
console.log(testIter.next())

// { value: 1, done: false }
// { value: 2, done: false }
// { value: undefined, done: true }

提前终止迭代器

可选的return()方法用于指定在迭代器提前关闭时执行的逻辑。迭代器内部调用了return()方法中止迭代。

可能会中止迭代的操作:

  • for-of循环通过break,continue,return或throw提前退出。
  • 解构操作并未消费所有值。

在自定义迭代器中模拟:

class Counter {
    //size为最大值
    constructor(size){
        this.size = size
    }

    //迭代器工厂函数
    [Symbol.iterator](){
        let value = 1
        let size = this.size
        return {
            next(){//实现自定义next()功能---使用了闭包
                if(value <= size){
                    return {value:value++,done:false}
                }else{
                    return {value:undefined,done:true}
                }
            },
            return(){
                console.log('迭代终止')//提示
                return {done:true}//并返回值
            }
        }
    }
}

let test = new Counter(5)

for(let i of test){
    if(i === 3){
        break;
    }
    console.log(i)
}

// 1
// 2
// 迭代终止

数组的迭代器是不能关闭的。

生成器

生成器是一种极为灵活的代码结构,拥有在一个函数块内暂停和恢复执行的能力。

什么是生成器?

生成器函数:

定义生成器:在函数前面加一个*号

//生成器的形式是一个函数,所有只需要在函数声明的基础上加一个*号
function* fn(){}
function *fn(){}
function * fn(){}

箭头函数不能用来自定义生成器。

生成器对象:

  • 调用生成器函数,会生成一个生成器对象。

  • 生成器对象一开始处于暂停执行状态

  • 生成器也实现了Iterator接口,所有具备next()方法,调用next()方法,会让执行器开始或恢复执行

生成器的基本使用

  • 返回生成器
  • next()
//定义一个生成器方法
function* fn(){
    return '测试!!'
}

//调用生成器方法,返回一个生成器对象
let test = fn()

console.log(test)//Object [Generator] {} --- 证明生成器对象确实是一个对象
console.log(test.next)//[Function: next] --- 证明具备next方法

//初次调用next()---开始执行生成器
//调用next()返回一个对象{value:生成器函数的返回值,done:是否已不可继续获取}
console.log(test.next())//{ value: '测试!!', done: true }

通过yield关键字中断执行

  • yield关键字可以让生成器停止和开始执行

  • 生成器函数在遇到yield关键字之前---会正常执行

  • 生成器函数在遇到yield关键字之后---执行会停止,函数作用域的状态会被保留。

  • 停止执行的生成器函数只能通过生在成器对象上调用next()方法来恢复执行

//定义一个生成器方法
function* fn(){
    yield '断定1'
    yield '断定2'
    return '测试!!'
}

//调用生成器方法,返回一个生成器对象
let test = fn()

console.log(test.next())//{ value: '断定1', done: false }
console.log(test.next())//{ value: '断定2', done: false }
console.log(test.next())//{ value: '测试!!', done: true }//可理解为没有了yield,done就为true

yield关键字只能用于生成器内部,用在其他地方会报错!!

将生成器作为可迭代对象

function* fn(){
    yield '断定1'
    yield '断定2'
    return '测试!!'
}

for(let i of fn()){
    console.log(i)
}
// 断定1
// 断定2

使用yield实现输入和输出

由外向内传值:

function* fn(){
    console.log(yield)
    console.log(yield)
    console.log(yield)
}

let test = fn()

test.next('第一个参数无效')
test.next('测试值1')
test.next('测试值2')

// 测试值1
// 测试值2

产生可迭代对象--yield的合并写法

使用*数组号实现

function* fn(){
    yield * ['断定1','断定2']
    //与yield '断定1';yield '断定2'等效
}

for(let i of fn()){
    console.log(i)
}
// 断定1
// 断定2

生成器的返回值为done: true时的返回值,在迭代器中这个值为undefined。

使用yield*实现递归算法

function* fn(n){
    if(n>0){
        yield * fn(n-1)//使用yield *实现递归
        yield n-1//使用yield提供等待数据
    }
}

for(let i of fn(3)){
    console.log(i)
}
// 0
// 1
// 2

生成器可以作为默认迭代器

原因:生成器对象实现了Iterable接口。

提前终止生成器

生成器除了具备next()和return()方法外,还具备throw()方法

return()

return()方法强制生成器进入关闭状态。提供给return()方法的值,就是终止迭代器对象的值。

//定义一个生成器方法
function* fn(){
    yield * ['断点1','断点2','断点3']
}

let test = fn()
console.log(test.next())
console.log(test.return('结束!!'))
console.log(test.next())

// { value: '断点1', done: false }
// { value: '结束!!', done: true } ---return()中的参数为value值
// { value: undefined, done: true }

throw()

  • throw()方法会在暂停的时候将一个提供的错误注入到生成器中,如果错误未被处理,生成器就会关闭。
  • 如果生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。错误会跳过对应的yield。
function* fn(){
    yield * ['断点1','断点2','断点3']
}

let test = fn()
console.log(test.next())
console.log(test.throw('异常!!'))
console.log(test.next())

// { value: '断点1', done: false }
// D:\desktop\前端知识点\js\js每日一题\text.js:4
// }
// ^
// TypeError: The iterator does not provide a 'throw' method......