迭代器与生成器

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

理解迭代

在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. IPv6时代,中小企业该如何布局?

    ​简介:IPv6要为全世界的每一粒沙子都分配一个IP,你的企业跟上了吗? 11月中旬,中央网信办等部门联合印发了<关于开展IPv6技术创新和融合应用试点工作的通知>,联合组织开展IPv6技 ...

  2. 阿里云容器服务全面升级为 ACK Anywhere,让云的边界拓展至企业需要的每个场景

    ​简介: 2021 年 9 月 26 日上海阿里云计算峰会上,阿里巴巴研究员.阿里云云原生应用平台负责人丁宇宣布,阿里云容器服务全面升级为 ACK Anywhere,让企业在任何需要云的地方,都能获得 ...

  3. 鸿蒙HarmonyOS实战-ArkUI动画(页面转场动画)

    前言 页面转场动画是指在应用程序中,当用户导航到另一个页面时,使用动画效果来过渡页面之间的切换.这样做的目的是为了提升用户体验,使页面之间的切换更加平滑和有趣. 常见的页面转场动画包括淡入淡出.滑动. ...

  4. [Roblox] 从障碍跑入门构建基础平台游戏_罗布乐思

      对于障碍跑来说,底板部分是可以不需要的 这样掉下障碍物就结束游戏,Baseplate 可以在右侧资源管理器 选中后Delete. SpawnLocation 标记玩家在游戏开始时或重新开始后在世界 ...

  5. WPF 已知问题 Separator 无法应用 ContextMenu 定义的默认样式

    本文记录一个 WPF 已知问题,在 ContextMenu 的 Resources 里定义 Separator 的默认样式,在 ContextMenu 里面的 Separator 将应用不上,或者说不 ...

  6. 如何参与 .NET 的开发和设计

    现在 dotnet 属于 dotnet 基金会,所有开发者都可以向 dotnet 贡献代码和参与 .NET 的设计,参与路线决策.本文来告诉大家一些基本玩法,带着小伙伴们入坑 注意哦,参与 dotne ...

  7. pde复习笔记 第一章 波动方程 第六节 能量不等式、波动方程解的唯一性和稳定性

    能量不等式 这一部分需要知道的是能量的表达式 \[E(t)=\int_{0}^{l}u_{t}^{2}+a^{2}u_{x}^{2} dx \] 一般而言题目常见的问法是证明能量是减少的,也就是我们需 ...

  8. win系统执行脚本报错策略更改无法加载文件 C:\Users\xx\AppData\Roaming\npm\pnpm.ps1,因为在此系统上禁止运行脚本

    Start-Process powershell -Verb runAs Get-ExecutionPolicy Get-ExecutionPolicy -List set-ExecutionPoli ...

  9. 扩展实现Unity协程的完整栈跟踪

    现如今Unity中的协程(Coroutine)方案已显得老旧,Unitask等异步方案可以做到异常捕获等yield关键字处理起来很麻烦的问题, 并且Unity官方也在开发一套异步方案,但对于临时加入到 ...

  10. 为什么下载程序的时候会提示win-amd64.exe