重学迭代器和生成器

news2024/11/24 15:00:15

重学迭代器和生成器

之前在 JavaScript 高级程序设计第 7 章 迭代器和生成器 学习笔记 其实包含过 iterator 和 generator 的学习笔记,不过依旧温故而知新,有了一些实际上手的经验后重新再回滚一边会有比较深刻的理解,而不是只是 cv 书上的内容。

这里丢一个 generator 实现无限拉取的效果,图在这里,代码在最后:

在这里插入图片描述

大抵效果是先加载一部分的文章/视频内容,数量可以由后端控制,如之前复刻 yt 的时候,好像有从 API 中注意到拉取视频的数量其实是由后端控制的:

在这里插入图片描述

如果是自己实现的话,思路大抵是这样的:用户在与前端有交互后(比如说点击 load more,或者用滚轮继续往下拉,通过 loading spin 进行更多拉取),通过 generator 获取下一部分的信息后,渲染到页面上。

protocols

这里说到的 protocol 有三(四)个:

  • iterator protocol

    要满足 iterator protocol,那么就必须要实现对象上的 next() 方法

    next() 返回的对象类型为:

    interface IteratorReturnResult<TReturn> {
      done: true;
      value: TReturn;
    }
    
    interface IteratorYieldResult<TYield> {
      done?: false;
      value: TYield;
    }
    
    type IteratorResult<T, TReturn = any> =
      | IteratorYieldResult<T>
      | IteratorReturnResult<TReturn>;
    
  • iterable protocol

    iterable 必须实现 @@iterator 方法

    @@iterator 的返回值如下:

    interface Iterator<T, TReturn = any, TNext = undefined> {
      // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
      next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
      return?(value?: TReturn): IteratorResult<T, TReturn>;
      throw?(e?: any): IteratorResult<T, TReturn>;
    }
    

    换言之,iterable protocol 的实现是必须基于 iterator protocol 上实现的

  • aync iterator protocol & async iterable protocol

    其实现方法大体与上面的没什么区别,不过需要实现的是 @@asyncIterator 方法而非@@iterator 方法

iterator

如果使用过其他的编程语言,应该会 iterator 不会太陌生。对可以使用 for...of 的对象来说,其 prototype chain 上必然有一个对象是实现了 @@iterator 方法的。

换言之,需要满足两个需求:

  1. 实现一个 next() 方法
  2. 实现 [Symbol.iterator] 方法

基础用法如下:

const arr = [1, 2, 3, 4, 5, 6];
const iterator = arr[Symbol.iterator]();
let res = iterator.next();

console.log(res);

res = iterator.next();

console.log(res);

在这里插入图片描述

可以看到,比起循环来说,iterator 的一个好处在意可以通过程序去暂停和继续迭代的过程。比如说一个使用案例可能是视频的片段播放。现在很少有视频是整个下载下来的,基本上都是播放到某个锚点的时候去抓下一段视频。这个时候就可以通过 iterator 去进行执行。又或者需求可能是要创建一个无限循环的 iterator,这点如果要使用 loop,那就只能用 while (true)for(;;) 去执行,但是这样逻辑也就只能添加到循环体内,对于后期的维护非常困难。

其实现的方法有如下:

class Counter {
  // 设定上限 和 下限
  constructor(limit) {
    this.counter = 1;
    this.limit = limit;
  }

  // 满足 即迭代的自我识别能力
  // 实现 迭代需要执行的方法
  // 满足 迭代器协议的实现方法—— next()
  next() {
    if (this.counter <= this.limit) {
      return { done: false, value: this.counter++ };
    } else {
      return { done: true, value: undefined };
    }
  }

  // 实现 可迭代协议 第2点
  // 即 Symbol.iterator 的实现
  [Symbol.iterator]() {
    return this;
  }
}

let counter = new Counter(3);

// for of 会调用迭代器方法
for (let i of counter) {
  console.log(i);
  // 1
  // 2
  // 3
}

这个方法的问题就在于,当迭代器走到尽头后,再次调用迭代器不会的结果也是 { done: true, value: undefined }。为了解决这个问题,其中一个实现方法是使用 closure:

class Counter {
  constructor(limit) {
    this.limit = limit;
  }

  [Symbol.iterator]() {
    let count = 1,
      limit = this.limit;
    return {
      // 通过闭包,每次调用 迭代器 时会生成一个新的计时器
      next() {
        if (count <= limit) {
          return { done: false, value: count++ };
        } else {
          return { done: true, value: undefined };
        }
      },
    };
  }
}

这样,每次调用 counter.[Symbol.iterator]() 都会产生一个新的 count,并且该方法也可以被复用。

iterator 的终止和报错

重新回顾一下 iterator 的返回值:

interface Iterator<T, TReturn = any, TNext = undefined> {
  // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
  next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
  return?(value?: TReturn): IteratorResult<T, TReturn>;
  throw?(e?: any): IteratorResult<T, TReturn>;
}

除了必须要实现的 next 之外,还有两个可以选的 returnthrow,两个处理方式是针对 iterator 的中断实现的操作。依旧用上面的 Conter 为例:

class Counter {
  constructor(limit) {
    this.limit = limit;
  }

  [Symbol.iterator]() {
    let count = 1,
      limit = this.limit;
    console.log(count);
    return {
      // 通过闭包,每次调用 迭代器 时会生成一个新的计时器
      next() {
        if (count <= limit) {
          return { done: false, value: count++ };
        } else {
          return { done: true, value: undefined };
        }
      },
      return(value) {
        console.log('Finished iterator early');
        return {
          done: true,
          value,
        };
      },
      throw(e) {
        console.log('error thrown', e);
        return {
          done: true,
          value: e,
        };
      },
    };
  }
}

调用方法如下:

const counter = new Counter(5);

for (const val of counter) {
  console.log(val);
  if (val > 2) break;
}

try {
  for (const val of counter) {
    if (val > 2) throw new Error('terminated');
  }
} catch (e) {}

const iter = counter[Symbol.iterator]();
iter.throw('Error occurred');

在这里插入图片描述

需要注意的是,在 for...of 中使用 breakthrow 最后触发的都是 return 而非 throw

注 ⚠️: 因为 return 是可选的,因此不是所有的 iterator 都可以被关闭,如 Array 的就不可以。

generator

generator 是一种特殊的 iterator,它所实现的方法是实现一个 带有 * 的非箭头函数:function* funcName() {},另外,* 两侧不受空格影响,因此 function * funcName(){}, function *funcName(){} 都是合法语法。

因为 generator 本身就是一种另类的 iterator,所以使用方法上来说是一致的:

function* generator() {}

const g = generator();

console.log(g === g[Symbol.iterator]()); // true

在这里插入图片描述

以及定义:

interface Generator<T = unknown, TReturn = any, TNext = unknown>
  extends Iterator<T, TReturn, TNext> {
  next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
  return(value: TReturn): IteratorResult<T, TReturn>;
  throw(e: any): IteratorResult<T, TReturn>;
  [Symbol.iterator](): Generator<T, TReturn, TNext>;
}

关键词 yield

虽然 generator extends 了 iterator,不过在实际开发场景中,很少会手动实现 next,而是使用 yield 去进行控制。具体流程为:

  • JS 执行 generator 中的代码
  • JS 遇到 yield 关键字后停止执行,但是相关联的作用于会被保留
  • 开发调用 g.next() 后,JS 返回 yield 后的值
  • 重复循环操作
  • 当没有可以 yield 的值后,generator 的返回值被改为 {value: undefined, done: true},并且会维持在这个状态

依旧以上面使用的 Counter 为例,对比一下 generator 的实现:

class Counter {
  constructor(limit) {
    this.limit = limit;
  }

  *generator() {
    let count = 1;
    try {
      while (count < this.limit) {
        yield count++;
      }
    } catch (e) {
      yield 'Error occurred';
    } finally {
      yield 'Generator done';
    }
  }
}

const counter = new Counter(5);
let iter = counter.generator();
for (const value of iter) {
  console.log(value);
}

可以看到,generator 的实现稍微简单一些,但是,只是简单的 loop 所有的返回值,会出现结尾多一个 finally 中处理的值:

在这里插入图片描述

这里可能就会要求开发手动进行一些的判断,保证“错误”的值不会被显示出来。

可以接受参数

与普通的 iterator 不同,generator 其实是可以接受参数的,如:

  *generator() {
    let count = 1;
    let nextCounter;
    try {
      while (count < this.limit) {
        nextCounter =
          yield `current counter: ${count++}, nextCounter is: ${nextCounter}`;
      }
    } catch (e) {
      console.log(e);
      yield 'Error occurred';
    } finally {
      yield 'Generator done';
    }
  }

while (true) {
  const { value, done } = iter.next(anotherCounter++);
  console.log(anotherCounter);
  console.log(value);

  if (done) break;
}

在这里插入图片描述

yield 可以接受从 next 中传进来的参数,这也让 generator 的使用更加的灵活。

yield 一个可迭代对象

这个写法也是这次复习的时候才看到的,前面真的囫囵吞枣,没看的特别仔细就直接跳过去了:

function* generator() {
  yield* [1, 2, 3, 4, 5];
  // 等同于
  // yield 1
  // yield 2
  // yield 3
}

const g = generator();

for (const val of g) {
  console.log(val);
}

在这里插入图片描述

开始的案例

这里主要实现的是 asyncIterator,HTML 部分主要就是一点点的 CSS 和 button,这里不多赘述。

JS 如下:

class Posts {
  wait(delay) {
    return new Promise((resolve) => {
      setTimeout(resolve, delay);
    });
  }

  // 实现 asyncIterator
  // 这里虽然用不到,不过实现了 asyncIterator 应该也可以使用 for await...of 的语法
  async *fetchPosts() {
    let id = 1;

    // while (true) 为必须条件,否则 generator 在没有可以 yield 的东西后就会被关闭
    while (true) {
      await this.wait(500);
      const post = (await fetch(`https://dummyjson.com/posts/${id}`)).json();
      yield post;
      id++;
    }
  }
}

const posts = new Posts();
const iter = posts.fetchPosts();
const postsList = document.getElementById('posts');

// UI 相关
const createPost = ({ id, body, title }) => {
  const postItem = document.createElement('li');
  postItem.id = id;

  const article = document.createElement('article');
  const titleEl = document.createElement('header');
  const paragraph = document.createElement('p');
  titleEl.innerHTML = title;
  paragraph.innerHTML = body;

  article.appendChild(titleEl);
  article.appendChild(paragraph);

  postItem.appendChild(article);
  postsList.appendChild(postItem);
};

// 先拉取几个post做demo
(async () => {
  for (let i = 0; i < 4; i++) {
    const res = await iter.next();
    createPost(res.value);
  }
})();

const fetchBtn = document.getElementById('fetch');
// 点击触发拉取事件
fetchBtn.addEventListener('click', async () => {
  const res = await iter.next();
  createPost(res.value);
});

保证 generator 一直是开着的状态对于无限拉取还是很重要的,否则 generator 关闭后就是这个状态:

在这里插入图片描述

这个情况下继续调用 generator.next() 并不会报错,只是返回值永远都是 {value: undefined, done: true}。因此在实际使用 generator 进行开发的时候,也是需要对返回值——特别是 done——进行一个判断。

reference

  • Use-Cases For JavaScript Generators

  • Redux Toolkit + React + TS + Tailwind CSS 复刻 YouTube 学习心得

  • 重学 Symbol

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/579388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

硬件基础常识【3】--详细说说贴片电容器,可能有你不知道的

目录 贴片电容介绍MLCC的制作过程电容失效的头号大敌电容失效的最主要原因电容的容值、耐压值与封装尺寸的关系 电容串并联串联并联 电容的等效电路选取电容的建议总结 贴片电容介绍 贴片电容相信干电子技术活的基本都使用过&#xff0c;他的全称为&#xff1a;多层片式陶瓷电…

基础学习——读txt数据、字符串转list或数组、画PR曲线、画Loss曲线

文章目录 字符串转数组字符串中的数组转列表转整数列表 读数据&#xff0c;然后画PR曲线读取txt数据关于PR曲线代码 读数据画Loss曲线读txt数据代码 字符串转数组 .split() 是Python中的一个字符串方法&#xff0c;它可以将一个字符串按照指定的分隔符分割成多个子字符串&…

智能工厂 | 联合汽车电子有限公司汽车驱动科技上海智能工厂

智能制造是我国加快建设制造强国的主攻方向&#xff0c;是上海城市数字化转型的重要抓手。智能工厂是推动智能制造的切入点和突破口&#xff0c;是制造业数字化转型的重要载体&#xff0c;以智能工厂为载体布局新赛道、触发新动能、带动新终端&#xff0c;从而实现制造业高质量…

scanf读取字符数组的注意点

起因&#xff1a;scanf的%c格式可以读取空格和回车 读取中间无空格隔开的二维字符数组时 #include<bits/stdc.h> using namespace std; char mp[10][10]; signed main() {for(int i1;i<3;i){for(int j1;j<3;j){scanf("%c",&mp[i][j]);}getchar();/…

Zookeeper集群 + Kafka集群

Zookeeper 概述 Zookeeper 定义 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心的…

液体压强、浮力与密度分析

如图所示&#xff0c;将甲、乙两个容器放在水平桌面上&#xff0c;甲、乙两容器的底面积为S甲&#xff0c;S乙&#xff0c;甲容器中盛有密度为p1的液体&#xff0c;乙容器中盛有密度为p2的液体。现将体积相等的A、B两个物体分别放入甲、乙两容器后&#xff0c;物体A悬浮&#x…

学习TypeScript快速入门

&#x1f341; 作者主页&#xff1a;&#x1f496;仙女不下凡&#x1f496; &#x1f341; 前言介绍&#xff1a;以下&#x1f447; 内容是根据“阿宝哥”的教材自学总结&#xff0c;定期更新欢迎持续关注&#xff01; &#x1f341; 学习前提&#xff1a;学习该文章之前需要…

数据在内存中的存储(1)——整形

目录 1、数据类型介绍 1.1、类型的基本归类 整形家族 浮点数家族 指针类型 空类型 构造类型 2、整形在内存中的存储 2.1、原码、反码、补码 2.2、大小端介绍 2.3、有符号与无符号 2.4、练习 例一 例二 例三 例四 例五 1、数据类型介绍 我们先来简单了解一下我们前面所学的基…

【腾讯云Finops Crane集训营】降本增效之 Crane 初体验

1. Crane 初识2. Crane 如何进行成本优化&#xff1f;3. Crane 快速上手体验3.1 安装 Prometheus 和 Grafana3.2 安装 Crane 和 Fadvisor3.3 验证安装是否成功3.4 访问 Dashboard 4. Crane 初体验 - 总结&建议5. 关于腾讯云 Finops Crane 集训营 最近有幸参加了腾讯云 Fino…

新星计划【Java微服务+云原生】赛道开启!

前排提醒&#xff1a;这里是新星计划2023【微服务云原生】学习方向的报名入口&#xff0c;一经报名&#xff0c;不可更换。 ↓↓↓报名入口&#xff1a;新星计划2023【微服务云原生】学习方向报名入口&#xff01;-CSDN社区 一、关于本学习方向导师 博客昵称&#xff1a;鹤冲…

opengl灯光基础:2.1 光照基础知识

光照&#xff1a; 光照以不同的方式影响着我们看到的世界&#xff0c;有时甚至是以很戏剧化的方式。当手电筒照射在物体上时&#xff0c;我们希望物体朝向光线的一侧看起来更亮。我们所居住的地球上的点&#xff0c;在中午朝向太阳时候被照得很亮&#xff0c;但随着地球的自转…

【使用VS开发的第一个QT项目——实现相机功能(包括QT下载、配置、摄像头程序)】

使用VS开发的第一个QT项目 一、QT(WIN10)安装1.首先下载QT(VS有对应的QT)2.安装QT 二、将QT加载到VS中三、QT设置1.在VS"Qt Vs Tools"→"QT Versions"中添加"msvc2017_64"qmake的路径2.在"General"→"QT Designer"中将"…

克里金插值(Kriging)在MATLAB中的实现【优化】

该部分是基于克里金插值&#xff08;Kriging&#xff09;在MATLAB中的实现&#xff08;克里金工具箱&#xff09;&#xff0c;由于在运行过程中有部分问题&#xff0c;基于此做的一些理解优化。 工具箱的下载见上面的链接&#xff0c;其提供了工具箱。 clc clearload(data_kr…

服务(第三十二篇)nginx做缓存服务器

nginx作为缓存服务配置语法 1、proxy_cache_path 配置语法&#xff08;即缓存路径配置语法&#xff09; Syntax&#xff1a;proxy_cache_path path [levelslevels] [use_temp_pathon|off] keys_zonename:size [inactivetime] [max_sizesize] [manager_filesnumber] [manager_s…

python爬虫笔记

Python爬虫笔记 一. Urllib 1. 基础请求 指定url请求返回值解码返回结果的一些操作 import urllib.request as req # 定义一个url url http://www.baidu.com# 发送请求获得相应 res req.urlopen(url)# read返回字节形式的二进制数据,需要用指定编码来解码 content res.r…

Allure测试报告定制全攻略,优化你的Web自动化测试框架!

目录 前言&#xff1a; 1. Allure测试报告简介 2. Web自动化测试框架简介 3. 封装Web自动化框架 3.1 安装Selenium 3.2 封装Selenium 3.3 定制Allure测试报告 3.3.1 适配翻译插件 3.3.2 定制测试报告样式 4. 示例代码 5. 总结 前言&#xff1a; 随着现在Web应用的普…

【特征选择】基于二进制粒子群算法的特征选择方法(PNN概率神经网络分类)【Matlab代码#33】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 特征选择问题2. 二进制粒子群算法3. 概率神经网络&#xff08;PNN&#xff09;分类4. 部分代码展示5. 仿真结果展示6. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第6节&…

day42_jsp

今日内容 零、 复习昨日 一、JSP 二、EL 三、JSTL 四、MVC 零、 复习昨日 一、JSP 1.0 引言 现有问题 在之前学习Servlet时&#xff0c;服务端通过Servlet响应客户端页面&#xff0c;有什么不足之处&#xff1f; 开发方式麻烦&#xff1a;继承父类、覆盖方法、配置Web.xml或注…

8.3:加强堆的应用

8.3&#xff1a;加强堆的应用 题目要求&#xff1a; 做一个加强堆的题目&#xff0c;给定一个整型数组&#xff0c;int[] arr&#xff1b;和一个布尔类型数组&#xff0c;boolean[] op 两个数组一定等长&#xff0c;假设长度为N&#xff0c;arr[i]表示客户编号&#xff0c;op…

【程序人生】上海城市开发者社区小聚有感

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…