Heim  >  Artikel  >  Web-Frontend  >  Eine ausführliche Erklärung von Iterator- und for-of-Schleifen in JavaScript_Grundkenntnisse

Eine ausführliche Erklärung von Iterator- und for-of-Schleifen in JavaScript_Grundkenntnisse

WBOY
WBOYOriginal
2016-05-16 15:48:471023Durchsuche

Wie durchläuft man die Elemente eines Arrays? Vor 20 Jahren, als JavaScript auf den Markt kam, hätten Sie vielleicht Folgendes getan:

for (var index = 0; index < myArray.length; index++) {
 console.log(myArray[index]);
}
 
for (var index = 0; index < myArray.length; index++) {
 console.log(myArray[index]);
}

Seit ES5 können Sie die integrierte forEach-Methode verwenden:

JavaScript
myArray.forEach(function (value) {
 console.log(value);
});
 
myArray.forEach(function (value) {
 console.log(value);
});

Der Code ist schlanker, es gibt jedoch einen kleinen Nachteil: Sie können die Break-Anweisung nicht verwenden, um aus der Schleife zu springen, und Sie können auch nicht die Return-Anweisung verwenden, um von der Abschlussfunktion zurückzukehren.

Es wäre viel praktischer, wenn es eine for-Syntax zum Durchlaufen des Arrays gäbe.

Wie wäre es also mit der Verwendung von for-in?

for (var index in myArray) { // 实际代码中不要这么做
 console.log(myArray[index]);
}

 
for (var index in myArray) { // 实际代码中不要这么做
 console.log(myArray[index]);
}

Das ist nicht gut, weil:

Die Indexvariable im obigen Code besteht aus Zeichenfolgen wie „0“, „1“, „3“ usw. und nicht aus numerischen Typen. Wenn Sie den Index einer Zeichenfolge verwenden, um an einigen Operationen teilzunehmen („2“ 1 == „21“), sind die Ergebnisse möglicherweise nicht wie erwartet.
Es werden nicht nur die Elemente des Arrays selbst durchlaufen, sondern auch die vom Benutzer hinzugefügten zusätzlichen (Expando-)Elemente. Wenn ein Array beispielsweise ein solches Attribut myArray.name hat, wird index="name angezeigt eine bestimmte Schleife. Darüber hinaus können sogar Eigenschaften in der Array-Prototypkette durchlaufen werden.
Das Unglaublichste ist, dass der obige Code in einigen Fällen die Array-Elemente in beliebiger Reihenfolge durchläuft.

Um es einfach auszudrücken: for-in ist darauf ausgelegt, Objekte zu durchlaufen, die Schlüssel-Wert-Paare enthalten, und ist nicht so freundlich zu Arrays.
Leistungsstarke For-of-Schleife

Denken Sie daran, was ich letztes Mal erwähnt habe: ES6 hat keinen Einfluss auf den normalen Betrieb vorhandener JS-Codes. Tausende von Webanwendungen verlassen sich bereits auf die For-In-Funktion und verlassen sich sogar auf For-In für die Eigenschaften von Arrays, also niemand hat jemals vorgeschlagen, die bestehende for-in-Syntax zu „verbessern“, um die oben genannten Probleme zu beheben. Die einzige Möglichkeit, wie ES6 dieses Problem löst, besteht darin, eine neue Schleifendurchlaufsyntax einzuführen.

Dies ist die neue Syntax:

for (var value of myArray) {
 console.log(value);
}
 
for (var value of myArray) {
 console.log(value);
}

Durch die Einführung der oben genannten for-in-Syntax sieht diese Syntax nicht besonders beeindruckend aus. Wir werden die Wunder des For-Of später im Detail vorstellen, jetzt müssen Sie nur noch wissen:

  • Dies ist die einfachste und direkteste Möglichkeit, ein Array zu durchlaufen
  • Vermeidet alle Fallstricke der for–in-Syntax
  • Im Gegensatz zu forEach() unterstützt es Break-, Continue- und Return-Anweisungen.

for–in wird verwendet, um die Eigenschaften eines Objekts zu durchlaufen.

for-of wird zum Durchlaufen von Daten verwendet – genau wie Elemente in einem Array.

Dies sind jedoch nicht alle Funktionen von for-of, es gibt unten noch weitere spannende Teile.
Andere Sammlungen, die for-of unterstützen

for-of ist nicht nur für Arrays konzipiert, sondern kann auch für arrayähnliche Objekte wie NodeList, eine Sammlung von DOM-Objekten, verwendet werden.

kann auch zum Durchlaufen einer Zeichenfolge verwendet werden, wodurch die Zeichenfolge als Sammlung von Unicode-Zeichen behandelt wird:

201572885849513.jpg (423×77)

Es funktioniert auch mit Map- und Set-Objekten.

Vielleicht haben Sie noch nie von Map- und Set-Objekten gehört, da es sich um neue Objekte in ES6 handelt und es später separate Artikel geben wird, in denen sie ausführlich vorgestellt werden. Wenn Sie diese beiden Objekte in anderen Sprachen verwendet haben, ist es viel einfacher.

Zum Beispiel können Sie ein Set-Objekt verwenden, um Array-Elemente zu deduplizieren:

JavaScript
// make a set from an array of words
var uniqueWords = new Set(words);
 
// make a set from an array of words
var uniqueWords = new Set(words);

Nachdem Sie ein Set-Objekt erhalten haben, werden Sie das Objekt höchstwahrscheinlich durchlaufen. Dies ist sehr einfach:

for (var word of uniqueWords) {
 console.log(word);
}
 
for (var word of uniqueWords) {
 console.log(word);
}

Kartenobjekte bestehen aus Schlüssel-Wert-Paaren und die Traversierungsmethode unterscheidet sich geringfügig. Sie müssen zwei unabhängige Variablen verwenden, um den Schlüssel bzw. den Wert zu erhalten:

for (var [key, value] of phoneBookMap) {
 console.log(key + "'s phone number is: " + value);
}
 
for (var [key, value] of phoneBookMap) {
 console.log(key + "'s phone number is: " + value);
}

Bisher wissen Sie bereits: JS unterstützt bereits einige Sammlungsobjekte und wird in Zukunft weitere unterstützen. Die for-of-Syntax ist für diese Sammlungsobjekte konzipiert.

for-of kann nicht direkt zum Durchlaufen der Eigenschaften eines Objekts verwendet werden. Wenn Sie die Eigenschaften eines Objekts durchqueren möchten, können Sie die for-in-Anweisung (hierfür wird for-in verwendet) verwenden folgende Methode:

// dump an object's own enumerable properties to the console
for (var key of Object.keys(someObject)) {
 console.log(key + ": " + someObject[key]);
}

 
// dump an object's own enumerable properties to the console
for (var key of Object.keys(someObject)) {
 console.log(key + ": " + someObject[key]);
}

内部原理

    “好的艺术家复制,伟大的艺术家偷窃。” — 巴勃罗·毕加索

被添加到 ES6 中的那些新特性并不是无章可循,大多数特性都已经被使用在其他语言中,而且事实也证明这些特性很有用。

就拿 for-of 语句来说,在 C++、JAVA、C# 和 Python 中都存在类似的循环语句,并且用于遍历这门语言和其标准库中的各种数据结构。

与其他语言中的 for 和 foreach 语句一样,for-of 要求被遍历的对象实现特定的方法。所有的 Array、Map 和 Set 对象都有一个共性,那就是他们都实现了一个迭代器(iterator)方法。

那么,只要你愿意,对其他任何对象你都可以实现一个迭代器方法。

这就像你可以为一个对象实现一个 myObject.toString() 方法,来告知 JS 引擎如何将一个对象转换为字符串;你也可以为任何对象实现一个 myObject[Symbol.iterator]() 方法,来告知 JS 引擎如何去遍历该对象。

例如,如果你正在使用 jQuery,并且非常喜欢用它的 each() 方法,现在你想使所有的 jQuery 对象都支持 for-of 语句,你可以这样做:

// Since jQuery objects are array-like,
// give them the same iterator method Arrays have
jQuery.prototype[Symbol.iterator] =
 Array.prototype[Symbol.iterator];

 
// Since jQuery objects are array-like,
// give them the same iterator method Arrays have
jQuery.prototype[Symbol.iterator] =
 Array.prototype[Symbol.iterator];

你也许在想,为什么 [Symbol.iterator] 语法看起来如此奇怪?这句话到底是什么意思?问题的关键在于方法名,ES 标准委员会完全可以将该方法命名为 iterator(),但是,现有对象中可能已经存在名为“iterator”的方法,这将导致代码混乱,违背了最大兼容性原则。所以,标准委员会引入了 Symbol,而不仅仅是一个字符串,来作为方法名。

Symbol 也是 ES6 的新特性,后面将会有单独的文章来介绍。现在你只需要知道标准委员会引入全新的 Symbol,比如 Symbol.iterator,是为了不与之前的代码冲突。唯一不足就是语法有点奇怪,但对于这个强大的新特性和完美的后向兼容来说,这个就显得微不足道了。

一个拥有 [Symbol.iterator]() 方法的对象被认为是可遍历的(iterable)。在后面的文章中,我们将看到“可遍历对象”的概念贯穿在整个语言中,不仅在 for-of 语句中,而且在 Map和 Set 的构造函数和析构(Destructuring)函数中,以及新的扩展操作符中,都将涉及到。
迭代器对象

通常我们不会完完全全从头开始去实现一个迭代器(Iterator)对象,下一篇文章将告诉你为什么。但为了完整起见,让我们来看看一个迭代器对象具体是什么样的。(如果你跳过了本节,你将会错失某些技术细节。)

就拿 for-of 语句来说,它首先调用被遍历集合对象的 [Symbol.iterator]() 方法,该方法返回一个迭代器对象,迭代器对象可以是拥有 .next 方法的任何对象;然后,在 for-of 的每次循环中,都将调用该迭代器对象上的 .next 方法。下面是一个最简单的迭代器对象:

var zeroesForeverIterator = {
 [Symbol.iterator]: function () {
 return this;
 },
 next: function () {
 return {done: false, value: 0};
 }
};
 
var zeroesForeverIterator = {
 [Symbol.iterator]: function () {
 return this;
 },
 next: function () {
 return {done: false, value: 0};
 }
};

在上面代码中,每次调用 .next() 方法时都返回了同一个结果,该结果一方面告知 for-of语句循环遍历还没有结束,另一方面告知 for-of 语句本次循环的值为 0。这意味着 for (value of zeroesForeverIterator) {} 是一个死循环。当然,一个典型的迭代器不会如此简单。

ES6 的迭代器通过 .done 和 .value 这两个属性来标识每次的遍历结果,这就是迭代器的设计原理,这与其他语言中的迭代器有所不同。在 Java 中,迭代器对象要分别使用 .hasNext()和 .next() 两个方法。在 Python 中,迭代器对象只有一个 .next() 方法,当没有可遍历的元素时将抛出一个 StopIteration 异常。但从根本上说,这三种设计都返回了相同的信息。

迭代器对象可以还可以选择性地实现 .return() 和 .throw(exc) 这两个方法。如果由于异常或使用 break 和 return 操作符导致循环提早退出,那么迭代器的 .return() 方法将被调用,可以通过实现 .return() 方法来释放迭代器对象所占用的资源,但大多数迭代器都不需要实现这个方法。throw(exc) 更是一个特例:在遍历过程中该方法永远都不会被调用,关于这个方法,我会在下一篇文章详细介绍。

现在我们知道了 for-of 的所有细节,那么我们可以简单地重写该语句。

首先是 for-of 循环体:

for (VAR of ITERABLE) {
 STATEMENTS
}
 
for (VAR of ITERABLE) {
 STATEMENTS
}

这只是一个语义化的实现,使用了一些底层方法和几个临时变量:

var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
 VAR = $result.value;
 STATEMENTS
 $result = $iterator.next();
}

 
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
 VAR = $result.value;
 STATEMENTS
 $result = $iterator.next();
}

上面代码并没有涉及到如何调用 .return() 方法,我们可以添加相应的处理,但我认为这样会影响我们对内部原理的理解。for-of 语句使用起来非常简单,但在其内部有非常多的细节。
兼容性

目前,所有 Firefox 的 Release 版本都已经支持 for-of 语句。Chrome 默认禁用了该语句,你可以在地址栏输入 chrome://flags 进入设置页面,然后勾选其中的 “Experimental JavaScript” 选项。微软的 Spartan 浏览器也支持该语句,但是 IE 不支持。如果你想在 Web 开发中使用该语句,而且需要兼容 IE 和 Safari 浏览器,你可以使用 Babel 或 Google 的 Traceur 这类编译器,来将 ES6 代码转换为 Web 友好的 ES5 代码。

对于服务器端,我们不需要任何编译器 — 可以在 io.js 中直接使用该语句,或者在 NodeJS 启动时使用 --harmony 启动选项。

{done: true}

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn