• JavaScript红宝书第七章:迭代器与生成器


    理解迭代

    什么是迭代?迭代就是有条件的循环,按顺序访问可迭代结构中的每一项。如下图:

    for(let i=0;i<5;i++){
    	console.log(i)
    }
    
    • 1
    • 2
    • 3

    实现了一个最简单的迭代计数循环。但如果说我们想用一个迭代方法去迭代多种不同数据类型的内容,ES5新增了ForEach方法往通用迭代迈进了但仍不理想。

    let arr=[1,2,3,4]
    arr.forEach(item=>console.log(item))//1,2,3,4
    
    • 1
    • 2

    它解决了提取数组索引,和数组值问题,但是不能标识迭代数组的终止条件。所以ES6新增了一个迭代器模式,解决了通用迭代功能。

    迭代器模式

    迭代器模式是一个方案,它把有些结构称为可迭代对象,而这些对象都实现了Iterable接口,可以通过Iterator迭代器消费。而iterator是按需创建的一次性对象。iterrator迭代器会暴露关联的可迭代对象的迭代API,实现无需知道可迭代对象结构就能实现迭代。

    可迭代对象

    具有有限元素且无歧义遍历顺序的对象叫可迭代对象。

    可迭代协议

    实现迭代API(可迭代协议)需要具备两个能力:

    • 支持迭代的自我识别能力
    • 创建实现IteratorAPI的对象能力
      所以说,每个可迭代对象都有一个默认迭代器属性,这个属性名必须是Symbol.Iterator且这个属性必须引用一个迭代器工厂函数,调用工厂函数必须返回一个新迭代器。

    什么是工厂函数?

    工厂函数类似于自动化机器,往这个函数里面投放它需要的参数就可以得到想要的产品。

    实现Iterator接口的内置类型

    • 字符串
    • 数组
    • 映射
    • 集合
    • arguments对象
    • NodeList等Dom集合类型

    什么是arguments?

    可以移步我简述arguments的博客:原生JavaScript之函数特殊对象arguments

    如何检查是否有迭代接口以及工厂函数

    通过检查是否有[Symbol.Iterator]默认属性,来判断是否存在工厂函数。

    let arr=[1,2,3,4]
    	let str='123'
    	let num=123
    	console.log(arr[Symbol.iterator]);//values() { [native code] }
    	console.log(str[Symbol.iterator]);// [Symbol.iterator]() { [native code] }
    	console.log(num[Symbol.iterator]);// undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    而通过在这个默认属性加()即可调用迭代工厂函数。

    let arr=[1,2,3,4]
    let str='123'
    console.log(arr[Symbol.iterator]());// ay Iterator {}
    console.log(str[Symbol.iterator]());// ingIterator {}
    
    • 1
    • 2
    • 3
    • 4

    实现写代码过程中,我们不需要通过调用工厂函数生成迭代器,可以直接调用可迭代对象通用的方法特性来实现迭代:

    • for of
    • 数组解构
    • 扩展操作符
    • Array.from()
    • 创建集合
    • 创建映射
    • Promise.all
    • Promise.race
    • yield *(生成器)

    实例:

    let arr=[1,2,3,4]
    // for of
    for(item of arr){
    	console.log(item);//1 2 3 4
    }
    // 数组解构
    let [a,b,c]=arr
    console.log(a,b,c);//1 2 3
    // 扩展操作符
    let arr2=[...arr]
    console.log(arr2);//[1,2,3,4]
    // Array.from
    let arr3=Array.from(arr)
    console.log(arr3);//[1,2,3,4]
    // Set 构造函数
    let set = new Set(arr); 
    console.log(set); // Set(4) {'1', '2', '3',4} 
    // Map 构造函数
    let pairs = arr.map((x, i) => [x, i]); 
    console.log(pairs); // [['1', 0], ['2', 1], ['3', 2],[4,3]] 
    let map = new Map(pairs); 
    console.log(map); // Map(3) { '1'=>0, '2'=>1, '3'=>2 ,4=>3}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    迭代器协议

    迭代器是一次性使用对象,它的API使用next方法进行迭代遍历,next一次,则返回一个迭代结果对象,包含迭代器下一个返回值,不调用next就不知道当前迭代器位置。

    next方法

    调用next方法返回对象的属性:done和value
    done是用于判断是否还有下一个值的布尔(有则false,无则true),value是下一个值,没有则是undefined。

    let arr = ['foo', 'bar']; 
    let iter1 = arr[Symbol.iterator](); 
    console.log(iter1.next()); // { done: false, value: 'foo' } 
    console.log(iter1.next()); // { done: false, value: 'bar' }
    console.log(iter1.next()); // {value: undefined, done: true}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    自定义迭代器

    class Counter { 
     // Counter 的实例应该迭代 limit 次
     constructor(limit) { 
     this.count = 1; 
     this.limit = limit; 
     } 
     next() { 
     if (this.count <= this.limit) { 
     return { done: false, value: this.count++ }; 
     } else { 
     return { done: true, value: undefined }; 
     } 
     } 
     [Symbol.iterator]() { 
     return this; 
     } 
    } 
    let counter = new Counter(3); 
    for (let i of counter) { 
     console.log(i); 
    } 
    // 1
    // 2
    // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    可以自己实现一个自定义迭代,里面有迭代的返回条件以及对应的返回值。这里就不赘述了。

    提前终止迭代器

    不想遍历到终止,想要提前终止怎么办?
    for-of可以通过break、continue、return、throw提前退出。也可以自己设置一些终止条件来进行判断是否终止。
    如果我们不想终止迭代,但是想暂停某个自定义迭代函数块,在特定条件下暂停或者继续,就可以用到生成器来进行操作。

    生成器

    定义

    ES6新增结构,拥有在一个函数块内暂停和恢复代码执行的能力。这种新能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。

    // 生成器函数声明
    function* generatorFn() {} 
    // 生成器函数表达式
    let generatorFn = function* () {} 
    // 作为对象字面量方法的生成器函数
    let foo = { 
     * generatorFn() {} 
    } 
    // 作为类实例方法的生成器函数
    class Foo { 
     * generatorFn() {} 
    } 
    // 作为类静态方法的生成器函数
    class Bar { 
     static * generatorFn() {} 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注:箭头函数不能用来定义生成器函数。
    生成器函数声明就是在普通函数名前加*即可。而调用这个生成器会出现一个生成器对象,该对象初始为暂停执行状态。同时它也实现了Iterator接口,也有next方法,调用next会让生成器开始或恢复执行,同样next有两个属性done和value,当生成器函数体为空时done为true,value为undefined,想要修改默认value,可通过修改返回值来不让空函数体默认为undefined。

    function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
      let iterationCount = 0;
      for (let i = start; i < end; i += step) {
        iterationCount++;
        yield i;
      }
      return iterationCount;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上述代码中的yield关键字,用于生成器中断执行和继续执行。通过yield关键字退出的生成器,返回的done是false。

    function* a(){
    	yield;
    }
    console.log(a().next())//done:false,value:undefined
    
    • 1
    • 2
    • 3
    • 4

    注:yield关键字只能在生成器函数内部使用。

    yield可以干嘛

    生成器对象可以作为可迭代对象
    function* generatorFn(){
     yield 1; 
     yield 2; 
     yield 3; 
    } 
    for (const x of generatorFn()) { 
     console.log(x); 
    } 
    // 1 
    // 2 
    // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    使用 yield 实现输入和输出
    function* generatorFn(initial) { 
     console.log(initial); 
     console.log(yield); 
     console.log(yield); 
    } 
    let generatorObject = generatorFn('foo'); 
    generatorObject.next('bar'); // foo 
    generatorObject.next('baz'); // baz 
    generatorObject.next('qux'); // qux
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    也可以同时输入输出

    function* generatorFn() { 
     return yield 'foo'; 
    } 
    let generatorObject = generatorFn(); 
    console.log(generatorObject.next()); // { done: false, value: 'foo' } 
    console.log(generatorObject.next('bar')); // { done: true, value: 'bar' }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    产生可迭代对象
    function* generatorFn() { 
     yield* [1, 2, 3]; 
    } 
    let generatorObject = generatorFn(); 
    for (const x of generatorFn()) { 
     console.log(x); 
    } 
    // 1 
    // 2 
    // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    使用 yield*实现递归算法
    function* nTimes(n) { 
     if (n > 0) { 
     yield* nTimes(n - 1); 
     yield n - 1; 
     } 
    } 
    for (const x of nTimes(3)) { 
     console.log(x); 
    } 
    // 0 
    // 1 
    // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    提前终止生成器

    return方法

    function* generatorFn() { 
     for (const x of [1, 2, 3]) { 
     yield x; 
     } 
    } 
    const g = generatorFn(); 
    console.log(g); // generatorFn {} 
    console.log(g.return(4)); // { done: true, value: 4 } 
    console.log(g); // generatorFn {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    throw方法

    function* generatorFn() { 
     for (const x of [1, 2, 3]) { 
     yield x; 
     } 
    } 
    const g = generatorFn(); 
    console.log(g); // generatorFn {} 
    try { 
     g.throw('foo'); 
    } catch (e) { 
     console.log(e); // foo 
    } 
    console.log(g); // generatorFn {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    Speedoffice(excel)文档中min函数如何使用
    弘玑Cyclone2022产品发布会:人机交互数智助手——智能助手CIRI
    671. 二叉树中第二小的节点
    PostgreSQL 10.23 安装图文教程
    中国石油大学(北京)-《 油层物理》第二阶段在线作业
    理解 Vue3 里的 defineProps 和 defineEmits
    Qt中正确的设置窗体的背景图片的几种方式
    jarsigner和apksigner对apk/aab签名
    全局异常捕获工具类
    【计算机视觉 | 目标检测 | 图像分割】arxiv 计算机视觉关于目标检测和图像分割的学术速递(9 月 11 日论文合集)
  • 原文地址:https://blog.csdn.net/lplovewjm/article/details/134266767