JavaScript高级程序设计(4)
第7章 迭代器与生成器
### 7.1 理解迭代 - “迭代”的意思是按照顺序反复多次执行一段程序,通常会有明确的终止条件。ECMAScript 6 规范新增了两个高级特性:迭代器和生成器。使用这两个特性,能够更清晰、高效、方便地实现迭代。 - 循环是迭代机制的基础,这是因为它可以指定迭代的次数,以及每次迭代要执行什么操作。计数循环就是一种最简单的迭代: ```javascript for (let i = 1; i <= 10; ++i) { console.log(i); } ``` - 迭代会在一个有序集合上进行。数组是 JavaScript 中有序集合的最典型例子。 ```javascript let collection = ['foo', 'bar', 'baz']; for (let index = 0; index < collection.length; ++index) { console.log(collection[index]); } //ES5 新增了 Array.prototype.forEach()方法 collection.forEach((item) => console.log(item)); ``` ### 7.2 迭代器模式 - 迭代器模式描述了一个方案,即可以把有些结构称为“可迭代对象”(iterable),因为它们实现了正式的 Iterable 接口,而且可以通过迭代器 Iterator 消费。 - 可迭代对象是一种抽象的说法。基本上,可以把可迭代对象理解成数组或集合这样的集合类型的对象。它们包含的元素都是有限的,而且都具有无歧义的遍历顺序 - 可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构。 - 任何实现 Iterable 接口的数据结构都可以被实现 Iterator 接口的结构“消费”(consume)。 - 迭代器(iterator)是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的 API。 ##### 7.2.1 可迭代协议 - 实现了 Iterable 接口的类型:字符串、数组、映射、集合、arguments对象 NodeList等DOM集合类型。 - 接收可迭代对象的原生语言特性包括:for-of 循环、数组解构、扩展操作符、Array.from()、创建集合、创建映射、Promise.all()、Promise.race()、yield*操作符。 ```javascript // 这两种类型没有实现迭代器工厂函数 let num = 1; let obj = {}; console.log(num[Symbol.iterator]); // undefined console.log(obj[Symbol.iterator]); // undefined // 这些类型都实现了迭代器工厂函数 let str = 'abc'; let arr = ['a', 'b', 'c']; let map = new Map().set('a', 1).set('b', 2).set('c', 3); let set = new Set().add('a').add('b').add('c'); let els = document.querySelectorAll('div'); console.log(str[Symbol.iterator]); // f values() { [native code] } console.log(arr[Symbol.iterator]); // f values() { [native code] } console.log(map[Symbol.iterator]); // f values() { [native code] } console.log(set[Symbol.iterator]); // f values() { [native code] } console.log(els[Symbol.iterator]); // f values() { [native code] } // 调用这个工厂函数会生成一个迭代器 console.log(str[Symbol.iterator]()); // StringIterator {} console.log(arr[Symbol.iterator]()); // ArrayIterator {} console.log(map[Symbol.iterator]()); // MapIterator {} console.log(set[Symbol.iterator]()); // SetIterator {} console.log(els[Symbol.iterator]()); // ArrayIterator {} ``` ##### 7.2.2 迭代器协议 迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。迭代器 API 使用 next()方法在可迭代对象中遍历数据。每次成功调用 next(),都会返回一个 IteratorResult 对象,其中包含迭代器返回的下一个值。若不调用 next(),则无法知道迭代器的当前位置。 ```javascript let arr = ['foo']; let iter = arr[Symbol.iterator](); console.log(iter.next()); // { done: false, value: 'foo' } console.log(iter.next()); // { done: true, value: undefined } console.log(iter.next()); // { done: true, value: undefined } console.log(iter.next()); // { done: true, value: undefined } ``` ##### 7.2.3 自定义迭代器 与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用。 ```javascript 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 }; } } }; } } ``` ##### 7.2.4 提前终止迭代器 可选的 return()方法用于指定在迭代器提前关闭时执行的逻辑。执行迭代的结构在想让迭代器知道它不想遍历到可迭代对象耗尽时,就可以“关闭”迭代器。可能的情况包括: - for-of 循环通过 break、continue、return 或 throw 提前退出; - 解构操作并未消费所有值。 ```javascript 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 }; } }, 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); } ``` ### 7.3 生成器 ##### 7.3.1 生成器基础 - 生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。只要是可以定义函数的地方,就可以定义生成器。 - 标识生成器函数的星号不受两侧空格的影响。 - 箭头函数不能用来定义生成器函数。 ```javascript // 生成器函数声明 function* generatorFn() {} // 生成器函数表达式 let generatorFn = function* () {} // 作为对象字面量方法的生成器函数 let foo = { * generatorFn() {} } // 作为类实例方法的生成器函数 class Foo { * generatorFn() {} } // 作为类静态方法的生成器函数 class Bar { static * generatorFn() {} } // 等价的生成器函数: function* generatorFnA() {} function *generatorFnB() {} function * generatorFnC() {} // 等价的生成器方法: class Foo { *generatorFnD() {} * generatorFnE() {} } ``` - 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next()方法。调用这个方法会让生成器开始或恢复执行。 ```javascript function* generatorFn() {} let generatorObject = generatorFn(); console.log(generatorObject); // generatorFn {<suspended>} console.log(generatorObject.next()); // { done: true, value: undefined } ``` ##### 7.3.2 通过 yield 中断执行 - yield 关键字可以让生成器停止和开始执行,也是生成器最有用的地方。生成器函数在遇到 yield关键字之前会正常执行。遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用 next()方法来恢复执行: ```javascript function* generatorFn() { yield; } let generatorObject = generatorFn(); console.log(generatorObject.next()); // { done: false, value: undefined } console.log(generatorObject.next()); // { done: true, value: undefined } ``` 1. 生成器对象作为可迭代对象 ```javascript function* generatorFn() { yield 1; yield 2; yield 3; } for (const x of generatorFn()) { console.log(x); } ``` 2. 使用 yield 实现输入和输出 ```javascript function* generatorFn() { return yield 'foo'; } let generatorObject = generatorFn(); console.log(generatorObject.next()); // { done: false, value: 'foo' } console.log(generatorObject.next('bar')); // { done: true, value: 'bar' } ``` 3. yield 定义一个无穷计数生成器函数: ```javascript function* generatorFn() { for (let i = 0;;++i) { yield i; } } let generatorObject = generatorFn(); console.log(generatorObject.next().value); // 0 console.log(generatorObject.next().value); // 1 console.log(generatorObject.next().value); // 2 console.log(generatorObject.next().value); // 3 console.log(generatorObject.next().value); // 4 console.log(generatorObject.next().value); // 5 ``` 4. 使用 yield\*实现递归算法 yield\*最有用的地方是实现递归操作,此时生成器可以产生自身。看下面的例子: ```javascript function* nTimes(n) { if (n > 0) { yield* nTimes(n - 1); yield n - 1; } } for (const x of nTimes(3)) { console.log(x); } ``` ##### 7.3.3 生成器作为默认迭代器 因为生成器对象实现了 Iterable 接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器格外适合作为默认迭代器。 ##### 7.3.4 提前终止生成器 三个方法:next()、return()、throw()。return()和 throw()方法都可以用于强制生成器进入关闭状态 ```javascript function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } } const g = generatorFn(); console.log(g); // generatorFn {<suspended>} console.log(g.next); // f next() { [native code] } console.log(g.return(4)); // { done: true, value: 4 } console.log(g); // generatorFn {<closed>} try { g.throw('foo'); } catch (e) { console.log(e); // foo } ```
顶部
收展
底部
[TOC]
目录
第1章 JavaScript简介
第2章 在 HTML中使用JavaScript
第3章 语言基础(1)语法变量
第3章 语言基础(2)数据类型
第3章 语言基础(3)操作符
第3章 语言基础(4)语句
第4章 变量、作用域与内存
第5章 基本引用类型
第6章 集合引用类型
第7章 迭代器与生成器
第8 章对象、类与面向对象编程
第9章 代理与反射
第10章 函数
第11章 期约与异步函数
第12章 BOM
第13章 客户端检测
第14章 DOM
第15章 DOM 扩展
第16章 DOM2 和 DOM3
第17章 事件
第18章 动画与 Canvas 图形
第19章 表单脚本
第20章 JavaScript API
第21章 错误处理与调试
第22章 处理 XML
第23章 JSON
第24章 网络请求与远程资源
第25章 客户端存储
第26章 模块
第27章 工作者线程
第28章 最佳实践
相关推荐
WebSocket