内置的迭代器
迭代器是 ES6 的一个重要部分,正因为此,你无须为许多内置类型创建你自己的迭代器,语言已经默认包含它们了。只有当内置的迭代器无法满足你的需要时,才有必要创建自定义迭代器,这最常发生在定义你自己的对象或类时,否则完全可以依靠内置的迭代器来完成工作。最常用的迭代器或许就是集合上的迭代器。
集合的迭代器
ES6 具有三种集合对象类型:数组、Map 与 Set。这三种类型都拥有如下的迭代器,有助于探索它们的内容:
-
entries():返回一个包含键值对的迭代器;
-
values():返回一个包含集合中的值的迭代器;
-
keys():返回一个包含集合中的键的迭代器。
你可以调用上述方法之一来提取集合中的迭代器。
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 ECMAScript 6");
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);
}
调用 console.log() 输出了以下内容:
[0, "red"]
[1, "green"]
[2, "blue"]
[1234, 1234]
[5678, 5678]
[9012, 9012]
["title", "Understanding ECMAScript 6"]
["format", "ebook"]
此代码在每种集合类型上使用了 entries() 方法来提取迭代器,并且使用 for-of 循环来迭代它们的项。此处的控制台输出说明了每个对象的键与值是如何被成对返回的。
values() 迭代器
values() 迭代器仅仅能返回存储在集合内的值,例如:
let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ECMAScript 6");
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 ECMAScript 6"
"ebook"
正如本例所显示的,调用 values() 迭代器返回了每种类型中包含的准确数据,而无须提供这些数据在集合内的任何位置信息。
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 ECMAScript 6");
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);
}
本例输出了如下内容:
0
1
2
1234
5678
9012
"title"
"format"
keys() 迭代器获取了 colors、tracking 与 data 各自的键,这些键在三个 for-of 循环中被打印出来。对于数组对象来说,只有数值类型索引被打印了,即使你向数组添加了具名属性也依然如此。这与在数组上使用 for-in 循环是不同的,因为 for-in 循环会迭代所有属性而不仅是数值索引。
集合类型的默认迭代器
当 for-of 循环没有显式指定迭代器时,每种集合类型都有一个默认的迭代器供循环使用。values() 方法是数组与 Set 的默认迭代器,而 entries() 方法则是 Map 的默认迭代器。在 for-of 循环中使用集合对象时,这些默认迭代器会让处理更容易一些。作为例子,研究如下代码:
let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();
data.set("title", "Understanding ECMAScript 6");
data.set("format", "print");
// same as using colors.values()
for (let value of colors) {
console.log(value);
}
// same as using tracking.values()
for (let num of tracking) {
console.log(num);
}
// same as using data.entries()
for (let entry of data) {
console.log(entry);
}
此处没有指定迭代器,因此默认的迭代器函数会被使用。数组、Set 与 Map 的默认迭代器反映了这些对象是如何被初始化的,于是此代码就输出了如下内容:
"red"
"green"
"blue"
1234
5678
9012
["title", "Understanding ECMAScript 6"]
["format", "print"]
数组与 Set 默认输出了它们的值,而 Map 返回的则是可以直接传给 Map 构造器的数组格式。另一方面,Weak Set 与 Weak Map 并未拥有内置的迭代器,使用弱引用意味着无法获知这些集合内部到底有多少个值,同时意味着没有方法可以迭代这些值。
|
解构与
此代码中的 |
字符串的迭代器
从 ES5 发布开始,JS 的字符串就慢慢变得越来越像数组。例如 ES5 标准化了字符串的方括号表示法,用于访问其中的字符(即:使用 text[0] 来获取第一个字符,以此类推)。不过方括号表示法工作在码元而非字符上,因此它不能被用于正确访问双字节的字符,正如此例所演示的:
var message = "A 𠮷 B";
for (let i=0; i < message.length; i++) {
console.log(message[i]);
}
此代码使用了方括号表示法与 length 属性来迭代字符串并打印字符,该字符串包含一个 Unicode 字符,输出结果有点出人意料:
A
(blank)
(blank)
(blank)
(blank)
B
// (blank) 代表空行
由于双字节字符被当作两个分离的码元来对待,此处的输出在 A 与 B 之间就有了四个空行。
幸好,ES6 旨在为 Unicode 提供完全支持(详见第二章),字符串的默认迭代器就是解决字符串迭代问题的一种尝试。这样一来,借助字符串默认迭代器就能处理字符而不是码元。将上个范例修改为使用字符串默认迭代器配合 for-of 循环,会得到更加合适的输出。以下是调整之后的代码:
var message = "A 𠮷 B";
for (let c of message) {
console.log(c);
}
此代码输出了如下内容:
A
(blank)
𠮷
(blank)
B
作用对象是字符,让本次的结果更符合预期:循环成功地打印出了这个 Unicode 字符以及其余字符。
NodeList 的迭代器
文档对象模型(DOM)具有一种 NodeList 类型,用于表示页面文档中元素的集合。对于需要书写在浏览器中运行的 JS 代码的开发者,要理解 NodeList 对象与数组之间的差异总是稍有困难。NodeList 对象与数组都使用了 length 属性来标明项的数量,并且都使用方括号表示法来访问各个项。然而本质上来说,NodeList 与数组的行为是完全不同的,这会引发许多混乱。
随着默认迭代器被附加到 ES6,DOM 关于 NodeList 的规定也包含了一个默认迭代器(此规定在 HTML 规范而非 ES6 规范中),其表现方式与数组的默认迭代器一致。这意味着你可以将 NodeList 用于 for-of 循环,或用于其他使用对象默认迭代器的场合。例如:
var divs = document.getElementsByTagName("div");
for (let div of divs) {
console.log(div.id);
}
此代码调用 getElementsByTagName() 来获取一个包含 document 对象中的所有 <div> 元素的 NodeList。接下来 for-of 循环迭代了每个元素并打印出它们的 ID,实际上这段代码与在标准数组上使用时并无二致。