迭代器与生成器

在软件开发领域,”迭代“的意思是按照顺序反复多次执行一段程序

理解迭代

在JavaScript中,计数循环就是最简单的迭代

但是这种迭代有点问题:

1. 迭代之前需要事先知道使用何种数据结构

2. 遍历顺序并不是数据结构固有的

后面js实现了Array.prototype.forEach()向通用迭代迈出了一步

ES6实现了迭代器模式

迭代器模式

迭代器模式描述了一个方案,即把有些结构称为可迭代对象,因为它们实现了正式的Iterable接口,并且可以通过迭代器Iterator消费

可迭代对象不一定非得是集合对象,也可以是有类似数组行为的其他数据结构

每一个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。

可迭代协议

实现Iterator接口(可迭代协议)需要同时具备两种能力:

  1. 支持迭代的自我识别能力
  2. 创建实现Iterator接口的对象的能力

    这意味着必须暴露一个属性作为默认迭代器,而且这个属性必须必须使用特殊的Symbol.iterator作为键。

我的理解:

上述提到实现了Iterable接口的对象称为可迭代对象,实际上可迭代对象实现的属性必须用Symbol.iterator作为属性,属性值是一个工厂函数,调用这个工厂函数,会返回一个新迭代器。

// 给Number实现的Iterator
let agg = 10;
Number.prototype[Symbol.iterator] = function() {
return {
next: function() {
return 'azoux iteration test'
}
}
}
ƒ () {
return {
next: function() {
return 'azoux iteration test'
}
}
}
agg[Symbol.iterator]()
{next: ƒ}

在实际写代码中不用显式地调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性


// 特性:
// for of // Promise.all // Promise.race() //yield let arr = [1, 2, 3]; // 数组解构 let [a, b, c] = [1, 2, 3]; // 扩展运算符 let arr2 = [...arr]; // Array.from(arr) // Set构造函数 // Map构造函数

迭代器协议

迭代器API使用next()方法在可迭代对象中遍历数据,每次成功调用都会返回一个IteratorResult对象

自定义迭代器

class Counter {

 constructor(limit) {

 this.count = 1;

 this.limit = limit;

 }

 next() {

 return this.count <= this.limit ? {

 done: false,

 value: this.count++

 } : {

 done: true,

 value: undefined

 };

 }

 [Symbol.iterator]() {

 return this;

 }

}

const counter = new Counter(3);

const it = counter[Symbol.iterator]();

console.log(it.next()); // { done: false, value: 1 }

for (let i of counter) console.log(i); // 2, 3

上面的类实现了Iterator接口,但是不太理想,因为它的每个实例都只能被迭代一次。

我们想要一个可迭代对象可以多次使用,因此可以考虑使用闭包,把计数器变量放到闭包中。

/**

 * 自定义迭代器

 */

class Counter {

 constructor(limit) {

 this.limit = limit;

 }

 [Symbol.iterator]() {

 let limit = this.limit;

 let count = 1;

 return {

 next() {

 return count <= limit ? {

 done: false,

 value: count++

 } : {

 done: true,

 value: undefined

 };

 }

 }

 }

}

const counter = new Counter(3);

for (let i of counter) console.log(i); // 1, 2, 3

console.log('span'); // 

for (let i of counter) console.log(i); // 1, 2, 3

提前终止迭代器

因为return()方法是可选的,所以并非所有迭代器都是可关闭的。

仅仅给一个不可关闭的迭代器加上return()方法并不能让它变成可关闭的

调用return()不会强制迭代器进入关闭状态

即便如此,return()方法还是会被调用

/**

 * 自定义迭代器

 */

class Counter {

 constructor(limit) {

 this.limit = limit;

 }

 [Symbol.iterator]() {

 let limit = this.limit;

 let count = 1;

 return {

 next() {

 return count <= limit ? {

 done: false,

 value: count++

 } : {

 done: true,

 value: undefined

 };

 },

 return () {

 console.log('Exiting early!');

 return {

 done: true

 };

 }

 }

 }

}

let counter1 = new Counter(5);

for (let i of counter1) {

 if (i > 2) break;

 console.log(i);

}

// 1

// 2

// Exiting early!

let counter2 = new Counter(5);

try {

 for (let i of counter2) {

 if (i > 2) throw 'err';

 console.log(i);

 }

} catch (e) {

 console.log(e);

}

// 1

// 2

// Exiting early!

// err

let arr = [1, 2, 3, 4, 5, 6, 7];

for (let i of arr) {

 if (i > 2) break; // 1, 2

 console.log(i);

}

console.log('---------------------'); // 1 2 3 4 5 6 7

for (let i of arr) console.log(i);

生成器

es6拥有在函数块内暂停和恢复代码执行的能力

生成器基础

  1. 生成器的形式是一个函数,函数名称前加上*号,来表示它是一个生成器。只要是能定义函数的地方都能定义生成器
  2. next方法的返回值类似迭代器,valued值默认为undefined,可以通过迭代函数返回值指定
  3. 生成器函数只会在初次调用next()方法后开始执行
/**

 * 生成器定义

 */

let gen1 = function* () {

}

function* gen2 () {

}

// and so on....
function* genFunc() {

 console.log('gen test');

 return 'azoux';

}

let gen = genFunc();

console.log(gen); // genFunc {<suspended>}

console.log(gen.next()); // gen test   {value: 'azoux', done: true}

console.log(gen); // genFunc {<closed>}

console.log(gen[Symbol.iterator]); // [Symbol.iterator]() { [native code] }

console.log(gen[Symbol.iterator]()); // genFunc {<closed>}

通过yield中断

  1. yield可以让生成器停止和开始执行
  2. 生成器函数在遇到yield之前会正常执行
  3. 遇到yield后会暂停执行,函数作用域的状态被保留,停止执行的生成器函数只能通过在生成器函数上调用next()方法来恢复执行
 * yield不能嵌套使用。只能出现在生成器函数内部使用

 */

function* genFunc() {

 yield 'azoux';

 yield 'domy';

 return 'the end';

}

const gen = genFunc();

console.log(gen.next()); // {value: 'azoux', done: false}

console.log(gen.next()); // {value: 'domy', done: false}

console.log(gen.next()); // {value: 'the end', done: true}

console.log(gen.next()); // {value: undefined, done: true}

/**

 * 生成器对象作为可迭代对象

 */

for (const c of genFunc()) console.log(c);

// azoux

// domy
/**

 * 使用yield实现输入输出

 * yield关键之会接收传给next()方法的第一个值

 * 第一次调用next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数

 */

function* genFunc(initial) {

 console.log(initial);

 console.log(yield);

 console.log(yield);

}

let gen = genFunc('step 0'); // step 0

gen.next('step 1');

gen.next('step 2');

gen.next('step 3');

function* nTimes(n) {

 for (let i = 0; i < n; i += 1) {

 yield i;

 }

}

for (let x of nTimes(5)) console.log(x);
/**

 * 产生可迭代对象

 * 用 * 增强yield的行为

 * yield* 实际上就是把一个可迭代对象序列化为一连串可以单独产出的值

 * yield* 的值是关联迭代器返回done: true时的value属性值

 * 对于生成器函数的迭代器来说,这个值就是生成器函数自己return的值

 */

function* genFunc() {

 yield*[1, 2, 3];

}

for (let k of genFunc())

 console.log(k); // 1 2 3

function* innerGen() {

 yield 'step 1';

 yield 'step 2';

 return 'step 3';

}

function* outerGen() {

 console.log('iter value: ', yield* innerGen());

}

for (let k of outerGen()) {

 console.log('value: ' + k);

}

// value: step 1

// value: step 2

// iter value:  step 3
/**

 * yield* 实现递归

 */

function* nTimes(n) {

 if (n > 0) {

 yield* nTimes(n - 1);

 yield n - 1;

 }

}

for (const c of nTimes(3)) console.log(c);

生成器作为默认迭代器

提前终止生成器

  1. return()
  2. throw()

Javascript高级程序设计第七章 | ch7 | 阅读笔记的更多相关文章

  1. javascript高级程序设计第三章的一些笔记

    [TOC] 1. 语法 1.1 区分大小写 变量.函数名和操作费都区分大小写. 1.2 标识符 标识符指变量.函数.属性的名字,或者函数的参数.标识符按以下规则组合: 第一个字符必须是一个字母,下划线 ...

  2. 《JavaScript高级程序设计》——第二章在HTML使用JavaScript

    这章讲的是JavaScript在HTML中的使用,也就是<script>元素的属性.书中详细讲了async.defer.src和type四个<script>的属性. 下面是对第 ...

  3. JavaScript 高级程序设计 第5章引用类型 笔记

    第五章 引用类型 一.object类型 1.创建方法: 1.使用new 操作符创建 var person=new object() Person.name=”Nicholasa” Porson.age ...

  4. JavaScript高级程序设计第20章JSON 笔记 (学习笔记)

    第二十章 JSON 1.Json 可以表示三种类型的值: 1.简单值: 表示数值:5  表示字符串:“hello wrold”注表示字符串时必须使用双引号 2.对象: {“name”:“mi”,”ag ...

  5. JavaScript高级程序设计第14章表单脚本 (学习笔记)

    第十四章 表单脚本 1.阻止默认表单提交 1.提交表单数据 1.使用type=submit提交按钮 2.使用submit():方法 注意:当用户点击提交按钮时,会触发submit事件,从而在这里我们有 ...

  6. 《JAVASCRIPT高级程序设计》第一章

    在使用调制解调器的时代,频繁的表单验证对客户端来说是一个很大的负担,javascript,作为一种专门进行表单验证的客户端脚本语言诞生了.到今天,javascript早已超越了当初设定的角色.Java ...

  7. 《JavaScript 高级程序设计》第一章:简介

    JavaScript 历史 JavaScript的诞生的主要是当时的 netspace 公司谋求为自己的浏览器 Navigator 添加一种脚本语言,以便在本地客户端进行一些行为操作,而这一功能的需求 ...

  8. 《JavaScript高级程序设计》——第一章JavaScript简介

    第一章主要讲了JavaScript的诞生和发展.刚刚接触JavaScript的我,似乎对这些内容并不感兴趣,快速看了一遍就开始去看第二章了. 看完第一章,收获也就是了解到JavaScript由ECMA ...

  9. javascript高级程序设计第5章,引用类型

    object类型: 创建object实列的方式有两种,一种是new()方法,一种是对象字面量表示法: 第一种法方:  var obj = new object(); obj.name = 'name' ...

  10. javascript高级程序设计第四章 变量、作用域和内存问题

    变量包含两种,,基本类型和引用类型 基本类型是指一些简单的字段: 引用类型是☞由多个值构成的对象  引用类型的值是保存在内存中的对象,在javascript中是不允许直接访问内存中的位置; 函数的参数 ...

随机推荐

  1. Apache Flink 在实时金融数据湖的应用

    简介: 本文由京东搜索算法架构团队分享,主要介绍 Apache Flink 在京东商品搜索排序在线学习中的应用实践 一.背景 在京东的商品搜索排序中,经常会遇到搜索结果多样性不足导致系统非最优解的问题 ...

  2. Modelsim使用指南

    Modelsim使用指南 本文讲述Modelsim的使用步骤. 添加一个测试文件,比如modulename.v. 编辑这个Verilog模块. 为了方便讲述,顶层模块名命名为"top&quo ...

  3. Java中使用try代码块自动关闭各种流(IO流……)

    推荐写在 try 的括号中 /** 方式一.方式二是等价的 * <li>推荐使用方式一</li> * @since 2023/5/18 0018 * @author CC ** ...

  4. 为什么我反对过度使用TypeScript?

    前言 在2024年, TypeScript肯定算不上什么新鲜的技术. 但是经过长时间的使用, 我认为可以使用, 但是要适度. 类型跟不上业务的变化 我们知道TypeScript的类型定义是业务的体现. ...

  5. 开源相机管理库Aravis例程学习(六)——camera-features

    目录 简介 例程代码 函数说明 arv_camera_get_integer arv_camera_get_string 简介 本文针对官方例程中的:04-camera-features做简单的讲解. ...

  6. Solution Set - 图上问题

    CF360E Link&Submission. 首先显然可以选择的边的权值一定会取端点值.事实上,第一个人经过的边选最小,第一个人不经过的边选最大,这样一定不劣.进一步,如果 \(s_1\) ...

  7. vue3.0 yarn启动项目

    linux 系统 在root账号下 yarn install yarn run serve 启动服务 ctrl+c //暂停服务 yarn build 打包服务 在公共目录里添加配置文件 优点:这样就 ...

  8. Linux中典型的文件权限问题

    总结起来说,可以打个比方,目录就像一间上了锁有窗户的屋子.如果你只想看屋子里面有啥,那么只要拥有r权限,不必进入到屋子,透过屋子的窗户就能看到里面的东西:但是如果你想改变屋子里面的物件,或者从屋子里面 ...

  9. ajax递归发送请求

    简介 大家都知道浏览器在处理http网络请求的时候,不同的浏览器会有不一样的并发限制,下表是一些主流浏览器对 HTTP 1.1 和 HTTP 1.0 的最大并发连接数目: Browser HTTP/1 ...

  10. addEventListener添加事件监听

    removeEventListener移除事件监听 window.addEventListener('mousedown', e => this.closeMenu(e)) window.add ...