[ES6深度解析]2:迭代器(Iterators )和for of循环
旧版本for循环
如何循环遍历数组中的元素?20年前,当JavaScript被引入时,你会这样做:
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
从ES5开始,你就可以使用内置的forEach方法:
myArray.forEach(function (value) {
console.log(value);
});
这个版本稍微短一些,但有一个小缺点:不能使用break语句跳出这个循环,也不能使用return语句从封闭函数返回。如果有一个简洁的for循环语法来循环数组元素,那就太好了。
for-in循环怎么样?
for (var index in myArray) { //不要这么做
console.log(myArray[index]);
}
这不是个好办法,原因如下:
- 在这段代码中,分配给index的值是字符串“0”、“1”、“2”等等,而不是实际的数字。由于不想触发字符串算术
(“2”+ 1 ==“21”)
,这方式是不方便的。 - 循环体不仅会对数组元素执行,还会对其他人添加的任何其他expando属性执行。例如,如果你的数组有一个可枚举的属性
myArray.name
,那么这个循环将额外执行一次index == "name"
。甚至可以访问数组原型链上的属性。 - 最令人惊讶的是,在某些情况下,该代码可以以任意顺序循环遍历数组元素。
简而言之,for-in
被设计用于带有字符串键的普通旧对象。对于数组,就没那么好了。
强大的for-of循环
ES6承诺不会破坏你已经编写的JS代码。数以百万计的网站依赖于for-in
的行为,甚至它在数组上的行为。因此,在数组中使用for-in
时,修复for-in并没有什么问题。ES6改进问题的唯一方法是添加某种新的循环语法。这就是:
for (var value of myArray) {
console.log(value);
}
- 这是迄今为止最简洁、最直接的数组元素循环语法
- 它避免了
for-in
的所有陷阱 - 与
forEach()
不同,它使用break
、continue
和return
for - in
循环用于遍历对象属性。
for - of
循环用于遍历数据,比如数组中的值。
其他集合也支持for-of
for - of
不仅仅适用于数组。它也适用于大多数数组类对象,如DOM节点列表。它也适用于字符串,将字符串作为Unicode字符序列处理:
for (var chr of "😺😲") {
alert(chr);
}
它也适用于Map
和Set
对象。
Set
对象有利于消除重复数据:
var uniqueWords = new Set(words);
for (var word of uniqueWords) {
console.log(word);
}
Map
略有不同:它内部的数据是由键值对组成的,所以你需要使用解构(destructuring)将键和值解压缩为两个独立的变量:
for (var [key, value] of phoneBookMap) {
console.log(key + "'s phone number is: " + value);
}
你可以看到:JS已经有相当多的不同的集合类,甚至更多的在路上。for-of
被设计为用于所有这些语句的主力循环语句。for - of
不能用于普通的旧对象,但如果你想遍历一个对象的属性,你可以使用for - in
(这就是它的用途)或内置的Object.keys()
:
// 输出一个对象所有可以枚举的属性值
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
for - of 本质
Good artists copy, great artists steal.” —Pablo Picasso
ES6中一个众所周知的秘密是,添加到语言中的新特性并不是凭空而来的。
大多数已经在其他语言中试用过,并被证明是有用的。例如,for - of
循环类似于c++
、Java
、c#
和Python
中的循环语句。与它们一样,它可以使用该语言及其标准库提供的几种不同的数据结构。但它也是语言中的一个扩展点。就像其他语言中的for/foreach
语句一样,for - of
完全基于方法调用。
数组,映射,集合,以及我们讨论过的其他对象的共同点是它们都有一个迭代器方法。还有另一种对象也可以有迭代器方法:任何你想要的对象。
就像你可以将myObject.tostring()
方法添加到任何对象中,然后JS突然知道如何将该对象转换为字符串一样,你可以将myObject[Symbol.iterator]()
方法添加到任何对象中,然后JS突然知道如何循环该对象。
例如,假设您正在使用jQuery,尽管你非常喜欢.each()
,但你也希望jQuery对象能够与for - of
一起工作。以下是如何做到这一点:
// 因为jQuery对象是类似数组的,所以给它们提供与数组相同的迭代器方法
jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
[Symbol.iterator]
语法看起来很奇怪。它与方法名有关。标准委员会可以把这个循环方法命名为.iterator()
,但是,现有的代码可能已经有一些带有.iterator()
方法的对象,这可能会非常令人困惑。因此,标准使用Symbol
而不是字符串.iterator()
作为这个方法的名称。
Symbol
在ES6中是新的特性。现在你只需要知道这个标准可以定义一个全新的符号,比如Symbol.iterator
,保证不会与任何现有代码冲突。需要权衡的是语法有点奇怪。但对于这个多功能的新特性和出色的向后兼容性来说,这只是一个很小的代价。
具有[Symbol.iterator]()
方法的对象称为iterable
。可迭代对象的概念在整个语言中被使用,不仅在for-of
中,而且在Map和Set构造函数、解构赋值和新的扩展操作符(spread operator)中。
迭代器对象
现在,我们可能永远不必从头开始实现自己的迭代器对象。但是为了完整起见,让我们看看迭代器对象是什么样子的。for-of
循环首先调用集合上的[Symbol.iterator]()
方法。返回一个新的迭代器对象。迭代器对象可以是任何具有.next()
方法的对象;for-of
循环将反复调用此方法,每次循环调用一次。例如,这可能是最简单的迭代器对象:
var zeroesForeverIterator = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
};
每次调用这个.next()
方法时,它都会返回相同的结果,告诉for-of
循环:
- 我们还没有完成迭代;
- 下一个值是0。
这意味着 for (value of zeroesForeverIterator) {}
将是一个无限循环。当然,典型的迭代器不会如此简单。
这种具有.done
和.value
属性的迭代器设计从表面上看与其他语言中的迭代器工作方式不同。在Java
中,迭代器有各自的. hasnext()
和.next()
方法。在Pytho
n中,它们有一个.next()
方法,当没有更多值时抛出StopIteration
。但这三种设计基本上都是返回相同的信息。
迭代器对象还可以实现可选的.return()
和.throw(exc)
方法。如果由于异常或break
或return
语句导致循环过早退出,则for-of
循环调用.return()
。如果迭代器需要做一些清理或释放它正在使用的资源,那么它可以实现.return()
。大多数迭代器对象都不需要实现它,.throw(exc)
更是一种特殊情况:for-of
根本不会调用它。
现在我们有了所有的细节,我们可以使用一个简单的for-of
循环,并根据底层的方法调用重写它。
首先是for-of循环:
for (VAR of ITERABLE) {
STATEMENTS
}
下面是一个大致的等效的写法,使用了底层方法和一些临时变量:
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
STATEMENTS
$result = $iterator.next();
}
本文来自博客园,作者:Max力出奇迹,转载请注明原文链接:https://www.cnblogs.com/welody/p/15167095.html
如果觉得文章不错,欢迎点击推荐