迭代器与生成器

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

理解迭代

在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. 慢sql治理经典案例分享

    ​简介:菜鸟供应链金融慢sql治理已经有一段时间,自己负责的应用持续很长时间没有慢sql告警,现阶段在推进组内其他成员治理应用慢sql.这里把治理过程中的一些实践拿出来分享下. ​ 作者 | 如期 来 ...

  2. MaxCompute中如何通过logview诊断慢作业

    ​建模服务,在MaxCompute执行sql任务的时候有时候作业会很慢,本文通过查看logview排查具体任务慢的原因 在这里把任务跑的慢的问题划分为以下几类 资源不足导致的排队(一般是包年包月项目) ...

  3. 37 手游基于 Flink CDC + Hudi 湖仓一体方案实践

    ​简介: 介绍了 37 手游为何选择 Flink 作为计算引擎,并如何基于 Flink CDC + Hudi 构建新的湖仓一体方案. 本文作者是 37 手游大数据开发徐润柏,介绍了 37 手游为何选择 ...

  4. 2019-10-18-C#-判断系统版本

    title author date CreateTime categories C# 判断系统版本 lindexi 2019-10-18 15:2:0 +0800 2018-03-08 17:34:3 ...

  5. golang中三种定时器的实现方式及周期定时

    一.定时器的创建 golang中定时器有三种实现方式,分别是time.sleep.time.after.time.Timer 其中time.after和time.Timer需要对通道进行释放才能达到定 ...

  6. NSThread的main方法内部做了什么?

    NSThread当调用start方法的时候,start方法就会调用main方法.那么这个main方法内部做了什么呢?下面是汇编码: 1 ;Foundation`-[NSThread main]: 2 ...

  7. cesium教程7-官方示例翻译-模型要素选择

    源代码示例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...

  8. threejs的坐标渲染和着色

    点击查看代码 function createBasic() { // 目标:了解顶点坐标绘制正方形 // 1. 准备 BufferGemotry 缓冲几何图形 // 2. 准备 32 位浮点数的数组, ...

  9. C 语言编程 — 高级数据类型 — 枚举

    目录 文章目录 目录 前文列表 声明枚举类型 定义枚举类型的变量 枚举类型变量的枚举值 枚举在 switch 语句中的使用 将整型转换为枚举类型 前文列表 <程序编译流程与 GCC 编译器> ...

  10. 撤销 git commit

    目录 文章目录 目录 场景1:撤回 commit,不撤销 git add .,保留代码 场景2:撤回 commit,撤销 git add .,保留代码 场景3:撤销 commit,撤销 git add ...