重学迭代器和生成器

重学迭代器和生成器

之前在 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python+pytest接口自动化之HTTP协议基础

目录 HTTP协议简介 HTTP协议特点 HTTP接口请求方法 HTTP与HTTPS区别 HTTP与TCP/IP区别 HTTP请求过程 总结 HTTP协议简介 HTTP 即 HyperText Transfer Protocol&#xff08;超文本传输协议&#xff09;&#xff0c;是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件…

【全面突击数据结构与算法001】绪论篇,数据结构的基本概念

&#x1f341;前言 &#x1f451;作者主页&#xff1a;&#x1f449;CSDN丨博客园 &#x1f3c6;学习交流&#xff1a;&#x1f449;在下周周ovoの社区 &#x1f48e;全面突击数据结构与算法系列专栏&#xff1a;&#x1f449;数据结构与算法专栏 PS&#xff1a;本篇文章主要综…

机器学习——线性回归篇

基本概念 什么是回归预测&#xff1f;什么是分类预测&#xff1f; 模型输入变量预测结果应用回归预测实值离散一个连续值域上的任意值预测值的分布情况分类预测实值离散两个或多个分类值将输入变量分类到不同类别 思考一个问题&#xff1a;分类问题是否可以转变为回归问题&am…

R:GAM非线性回归曲线拟合与散点密度图绘制

作者:CSDN @ _养乐多_ 本文将介绍使用R语言以及GAM模型,绘制回归曲线和散点密度图。 文章目录 一、R语言脚本二、色带一、R语言脚本 install.packages("ggpointdensity") install.packages("ggplot2") insta

问题解决:cmd中创建文件夹被拒绝访问。

问题&#xff1a; 在cmd中准备创建一个B盘node.js文件夹下的一个node_global文件被拒绝访问出错。 Microsoft Windows [版本 10.0.19045.2965] (c) Microsoft Corporation。保留所有权利。C:\Users\SueMagic>md B:\nodejs\node_global 拒绝访问。C:\Users\SueMagic>原因…

【分享】科大讯飞星火认知大模型(初体验)

前言&#xff1a; 哈喽&#xff0c;大家好&#xff0c;我是木易巷~ 随着人工智能技术的迅猛发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;成为了热门话题。在众多NLP模型中&#xff0c;科大讯飞星火认知大模型成为了一个备受瞩目的新秀&#xff0c;今天我们来了解…

【MySQL】MySQL的事务原理和实现?

文章目录 MySQL事务的底层实现原理一、事务的目的可靠性和并发处理 二、实现事务功能的三个技术2.1 redo log 与 undo log介绍2.1.1 redo log2.1.2undo log 2.2 mysql锁技术2.2.1 mysql锁技术 2.3 MVCC基础 三、事务的实现3.1 原子性的实现3.1.1 undo log 的生成3.1.2 根据undo…

书单 | IPD的12本书

随着IPD&#xff08;集成产品开发&#xff09;在IBM、华为等企业取得了巨大的成功&#xff0c;IPD逐渐被人们所知晓。诸多实践证明&#xff0c;IPD既是一种先进思想&#xff0c;也是一种卓越的产品开发模式&#xff0c;随着人们对IPD认识和探索&#xff0c;未来将会被应用到更多…

Window的创建

Window的创建 上一篇说到了Window和WindowManager的关系并且讲述了WindowManager如何添加Window与Window内部的三个方法的实现 这篇主要讲几个常见的Window的创建比如Activity,Dialog和Toast 其中Activity属于应用Window Dialog属于子Window Toast属于系统Window z-order…

git工作流实践

常见分支命名 远程仓库的分支&#xff1a;主干分支master, 开发分支dev&#xff0c;发布分支release 个人开发分支&#xff1a;特性分支feature, 缺陷修改分支bugfix&#xff0c; 热更新分支 hotfix 一般工作流如下 创建个人本地开发分支&#xff1a; git checkout -b feat…

springboot+vue+elementui计算机专业课程选课管理系统vue

本系统的主要任务就是负责对学生选课。主要用户为老师、学生,其中,学生可对自己的信息进行查询,可以进行选课,也可以进行删除已选课程,教师可对学生和课程的信息进行查询&#xff0c;教师拥有所有的权限,可以添加删除学生信息。系统提供界面,操作简单。 为实现这些功能,系统一个…

JAVA之数组2

添加元素 从后往前进行迭代&#xff0c;最后在末尾插入元素 tip&#xff1a;为避免数字在覆盖过程中丢失&#xff0c;故从后往前覆盖 删除元素 从前往后迭代&#xff0c;最后将末尾赋值为0 tip: 以覆盖的数arr【i】为基准&#xff0c;构造循环 共同处Tip: 范围均为【index1&…

【网络协议详解】——DHCP系统协议(学习笔记)

目录 &#x1f552; 1. DHCP概述&#x1f552; 2. 工作过程&#x1f552; 3. DHCP的报文格式&#x1f552; 4. DHCP中继代理&#x1f552; 5. 实验&#xff1a;DHCP配置 &#x1f552; 1. DHCP概述 动态主机配置协议DHCP&#xff08;Dynamic Host Configuration Protocol&…

SpringCloudAlibaba整合分布式事务Seata

文章目录 1 整合分布式事务Seata1.1 环境搭建1.1.1 Nacos搭建1.1.2 Seata搭建 1.2 项目搭建1.2.1 项目示意1.2.2 pom.xml1.2.2.1 alibaba-demo模块1.2.2.2 call模块1.2.2.3 order模块1.2.2.4 common模块 1.2.3 配置文件1.2.3.1 order模块1.2.3.2 call模块 1.2.4 OpenFeign调用1…

【C++】容器篇(四)—— queue的基本介绍以及模拟实现

前言&#xff1a; 在上期博文中我带大家对stack进行深入的学习&#xff0c;本期我将带领学习的是关于 queue的基本知识&#xff0c;并且还将给大家介绍并实现 priority_queue。接下来&#xff0c;让我们正式本期的内容。 目录 &#xff08;一&#xff09;queue的基本介绍 &…

Eclipse教程 Ⅵ

今天分享Eclipse Java 构建路径、Eclipse 运行配置(Run Configuration)和Eclipse 运行程序 Eclipse Java 构建路径 设置 Java 构建路径 Java构建路径用于在编译Java项目时找到依赖的类&#xff0c;包括以下几项&#xff1a; 源码包项目相关的 jar 包及类文件项目引用的的类…

83.响应式设计原则

什么是响应式设计&#xff1f; ● 使网页根据任何可能的屏幕尺寸&#xff08;窗口或视口尺寸&#xff09;调整其布局和视觉风格的设计技术。 ● 在实践中&#xff0c;这意味着响应式设计使网站可以在所有设备上使用&#xff0c;如台式电脑、平板电脑和手机。 ● 这是一套做法&…

LearnOpenGL-高级OpenGL-9.几何着色器

本人初学者&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 文章目录 几何着色器使用几何着色器造几个房子爆破物体法向量可视化 几何着色器 简介 在顶点和片段着色器之间有一个可选的几何着色器几何着色器的输入是一个图元&#xff08;如点或三角形&#xff09;的一…

Spark入门介绍

目录 一、Spark框架概述 1、Spark简介 2、发展 二、Spark功能及特点 1、定义

K8s进阶7——Sysdig、Falco、审计日志

文章目录 一、分析容器系统调用&#xff1a;Sysdig1.1. 安装1.2 常用参数1.3 采集分析1.4 示例1.4.1 查看某进程系统调用事件1.4.2 查看建立TCP连接事件1.4.3 查看某目录下打开的文件描述符1.4.4 查看容器的系统调用 1.5 Chisels工具1.5.1 网络类1.5.2 硬盘类1.5.3 cpu类1.5.4 …