八: ES6 迭代器 生成器
前言
该部分为书籍 深入理解ES6 第八章(迭代器与生成器)笔记
循环的问题
var colors = ["red", "green", "blue"];
for (var i = 0, len = colors.length; i < len; i++) {
console.log(colors[i]);
}
虽然这个循环非常直观, 然而当它被嵌套使用并要追踪多个变量时, 情况就会变得非常复杂. 额外的复杂度会引发错误, 而 for 循环的样板性也增加了自身出错的可能性, 因为相似的代码会被写在多个地方
何为迭代器?
ES6 中的迭代器是被设计专用于迭代的对象, 带有特定接口. 其规则为: 所有的迭代器对象都拥有 next() 方法, 会返回一个结果对象, 这个结果对象有两个属性: 对应下一个值的 value, 以及一个布尔类型的 done, 其值为 true 时表示没有更多值可供使用.
迭代器持有一个指向集合位置的内部指针, 每当调用了 next() 方法, 迭代器就会返回相应的 下一个值
// 在 ES5 中创建一个 类迭代器
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"
何为生成器?
生成器是能返回一个迭代器的函数, 生成器函数由放在 function 关键字之后的一个星号(*)来表示, 并能使用新的 yield 关键字. 星号紧跟在 function 关键字之后, 或是在中间留出空格, 都是没问题的
**生成器函数最有意思的方面可能就是它们会在每个 yield 语句后停止执行. ** yield 关键字可以和值或是表达式一起使用
// 在次函数内, for 循环在循环执行时从数组中返回元素给迭代器. 每当遇到 yield, 循环就会停止; 而每当 iterator 上的 next() 方法被调用, 循环就会再次执行到 yield 语句处.
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"
yield 关键字只能用在生成器内部, 用于其他任意位置都是语法错误 , 即使在生成器内部的函数也不行
function *createIterator(items) {
items.forEach(function(item) {
// 语法错误
yield item + 1;
});
}
生成器函数是 ES6 的一个重要特性, 并且因为它就是函数, 就能被用于所有可用函数的位置
1. 生成器函数表达式
**语法: *在 function 关键字与圆括号之间使用一个星号()即可.
注意: 箭头函数不能为生成器
let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"
2. 生成器对象方法
生成器就是函数, 因此可以被添加到对象中
var o = {
createIterator: function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
// ES6 的速记法
var o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
可迭代对象与for-of循环
与迭代器紧密相关的是, 可迭代对象(iterable)是包含 Symbol.iterator 属性的对象. 这个 Symbol.iterator 知名符号定义了为指定对象返回迭代器的函数.
在 ES6 中, 所有的集合对象(数组、Set与Map) 以及字符串都是可迭代对象, 因此它们都被指定了默认的迭代器, 可迭代对象被设计用于与 ES 新增的 for-of 循环配合使用
生成器创建的所有迭代器都是可迭代对象, 因为生成器默认就会为 Symbol.iterator 属性赋值
for-of 循环: 完全删除了追踪集合索引的需要, 专注于操作集合内容, 在循环每次执行时会调用可迭代对象的 next() 方法, 并将结果对象 value 值存储在一个变量上, 直到结果对象的 done 属性变成 true
let values = [1, 2, 3];
// 这个 for-of 首先调用了 values 数组的 Symobol.iterator 方法, 获取了一个迭代器(发生在 JS 引擎后台). 接下来 iterator.next() 被调用,迭代器结果对象的 value 属性被读出并放入了 num 变量。 num 变量的值开始为 1 ,接下来是 2 ,最后变成 3 。当结果对象的 done 变成 true ,循环就退出了,因此 num 绝不会被赋值为 undefined 。
for (let num of values) {
console.log(num);
}
1. 访问默认迭代器
可以使用 Symbol.iterator 来访问可迭代对象上的默认迭代器
let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 或检测是否能进行迭代
function isIterable(object) {
return typeof object[Symbol.iterator] === "function";
}
2. 创建可迭代对象
自定义对象默认情况下不是可迭代对象, 但可以通过写入 Symbol.iterator 属性, 让它们成为可迭代对象
let collection = {
items: [],
*[Symbol.iterator]() {
for (let item of this.items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for (let x of collection) {
console.log(x);
}
内置的迭代器
迭代器是 ES6 的一个重要部分, 语言已经为许多内置类型创建了迭代器.
1. 集合的迭代器
ES6 具有三种集合对象类型: Set、数组和Map. 这三种都拥有如下的迭代器,有助于探索它们的内容:
entries():返回一个包含键值对的迭代器
entries() 迭代器会在每次 next() 被调用时返回一个双项数组, 对于数组来说, 第一项是数值索引; 对于 Set, 第一项也是值(因为它的值也会被视为键); 对于 Map, 第一项就是键
let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ES6");
data.set("format", "ebook");
for (let entry of colors.entries()) {
console.log(entry);
}
for (let entry of tracking.entries()) {
console.log(entry);
}
for (let entry of data.entries()) {
console.log(entry);
}
// 此代码输出了如下内容:
[0, "red"]
[1, "green"]
[2, "blue"]
[1234, 1234]
[5678, 5678]
[9012, 9012]
["title", "Understanding ES6"]
["format", "ebook"]
values()迭代器
values() 迭代器仅仅能返回存储在集合内的值
let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ES6");
data.set("format", "ebook");
for (let value of colors.values()) {
console.log(value);
}
for (let value of tracking.values()) {
console.log(value);
}
for (let value of data.values()) {
console.log(value);
}
// 此代码输出了如下内容:
"red"
"green"
"blue"
1234
5678
9012
"Understanding ES6"
"ebook"
keys()迭代器
keys() 迭代器 能返回集合中的每一个键. 对于数组, 返回数值类型的 键, 永不返回数组的其他自由属性; Set 的键与值是相同的, 因此它的 keys() 与 values() 返回了相同的 迭代器; 对于 Map, keys() 迭代器返回了每个不重复的键
let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ES6");
data.set("format", "ebook");
for (let key of colors.keys()) {
console.log(key);
}
for (let key of tracking.keys()) {
console.log(key);
}
for (let key of data.keys()) {
console.log(key);
}
// 本例输出了如下内容:
1
2
1234
5678
9012
"title"
"format"
集合类型的默认迭代器
当 for-of 循环没有显式指定迭代器时, 每种集合类型都有一个默认的迭代器供循环使用
values() 方法是数组与 Set 的默认迭代器, 而 entries() 方法则是 Map 的默认迭代器.
2. 字符串的迭代器
从 ES5 发布开始, JS 的字符串就慢慢变得越来越像 数组. 而字符串也是由默认迭代器的
var message = "A B" ;
for (let c of message) {
console.log(c);
}
// 此代码输出了如下内容:
A
(blank)
(blank)
B
3. NodeList 的迭代器
文档对象模型(DOM)具有一种 NodeList 类型, 用于表示页面文档中元素的集合.
NodeList也包含了一个默认迭代器, 其表现方式与数组的默认迭代器一致.
var divs = document.getElementsByTagName("div");
for (let div of divs) {
console.log(div.id);
}
扩展运算符与非数组的可迭代对象
扩展运算符(...)可以被用于将一个 Set 转换为数组
扩展运算符能作用于所有可迭代对象, 并且会使用默认迭代器来判断需要使用哪些值. 所有的值都从迭代器中被读取出来并插入数组, 遵循迭代器返回值的顺序
let map = new Map([ ["name", "Nicholas"], ["age", 25]]),
array = [...map];
console.log(array); // [ ["name", "Nicholas"], ["age", 25]]
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]
迭代器的高级功能
使用迭代器的基本功能, 并使用生成器来方便地创建迭代器, 可以完成很多工作了, 然而, 在单纯迭代集合的值之外的任务中, 迭代器会显得更加强大
1. 传递参数给迭代器
前面的例子中已经展示了迭代器能够将值传递出来, 通过 next() 方法或者在生成器中使用 yield 都可以. 还能通过 next() 方法向迭代器传递参数 ==> 数据的双向流通
function *createIterator() {
// yield 1 => 会将语句的结果值(1)传递出去给第一个 next() 方法的结果对象
// (yield 1) => 表达式会接收到第二个 next(4) 传递进来的参数(4) 并赋值给 first 变量
let first = yield 1;
// yield first + 2 => 会将语句的结果值(6)传递出去给第二个 next(4) 方法的结果对象
// (yield first + 2) => 表达式会接收到第三个 next(5) 传递进来的参数(5) 并赋值给 second 变量
let second = yield first + 2; // 4 + 2
yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
执行机制图: 黄色表示对于 next() 的第一次调用、以及在生成器内部执行的所有代码;水蓝色表示了对next(4) 的调用以及随之执行的代码;而紫色则表示对 next(5) 的调用以及随之执行的代码
传递参数给迭代器.png
2. 在迭代器中抛出错误
能传递给迭代器的不仅是数据, 还可以是错误条件, 迭代器可以通过 throw() 方法, 用于指示迭代器应在回复执行时抛出一个错误
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // yield 4 + 2 ,然后抛出错误
yield second + 3; // 永不会被执行
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // 从生成器中抛出了错误
执行机制图: 红色表示当 throw() 被调用时所执行的代码,红星说明了错误在生成器内部大约何时被抛出。
在迭代器中抛出错误.png
可以在生成器内部使用一个 try-catch 块来捕捉错误
function *createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2; // yield 4 + 2 ,然后抛出错误
} catch (ex) {
second = 6; // 当出错时,给变量另外赋值
}
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
// throw() 方法就像 next() 方法一样返回了一个结果对象. 由于错误在生成器内部被捕捉, 代码继续执行到下一个 yield 处并返回了下一个值, 也就是 9
console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
next() 方法指示迭代器继续执行(可能会带着给定的值), 而 throw() 方法则指示迭代器通过抛出一个错误继续执行. 在调用点之后会发生什么, 根据生成器内部的代码来决定
next() 与 throw() 方法控制着迭代器在使用 yield 时内部的执行
3. 生成器的 Return 语句
生成器也是函数, 同样在它内部使用 return 语句, 这样会使生成器退出执行, 同样也可以指定在 next() 方法最后一次调用时的返回值
在生成器内, return 表明所有的处理已完成, 因此 done 属性会被设为 true, 而如果提供了返回值, 就会被用于 value 字段
function *createIterator() {
yield 1;
return 42;
yield 2;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
// 第二次调用时, return 语句后的 42 会被返回在 value 字段中
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
扩展运算符与 for-of 循环会忽略 return 语句所指定的任意值. 一旦它们看到 done 的值为 true, 它们就会停止操作而不会读取对应的 value 值
4. 生成器委托
生成器可以用星号()配合 yield 这一特殊形式来委托其他迭代器*
function *createNumberIterator() {
yield 1;
yield 2;
}
function *createColorIterator() {
yield "red";
yield "green";
}
// createCombinedIterator() 生成器依次委托了 createNumberIterator() 与 createColorIterator().
function *createCombinedIterator() {
yield *createNumberIterator();
yield *createColorIterator();
yield true;
}
var iterator = createCombinedIterator();
// 每次对 next() 的调用都会委托给合适的生成器, 直到使用 createNumberIterator() 与 createColorIterator() 创建迭代器全部清空为止
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
生成器委托也能让你进一步使用生成器的返回值, 这是访问这些返回值的最简单方式
function *createNumberIterator() {
yield 1;
yield 2;
// 这里只有 return 返回值才会被 result 接收, 使用 yield 是无效的
return 3;
}
function *createRepeatingIterator(count) {
for (let i=0; i < count; i++) {
yield "repeat";
}
}
function *createCombinedIterator() {
let result = yield *createNumberIterator();
yield *createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
可以直接在字符串上使用 yield *(例如 `yield * "hello"), 字符串的默认迭代器会被使用
异步任务运行
以后在进一步了解
发表评论 (审核通过后显示评论):