イテレータと for...of ループ
1. イテレータ
1.コンセプト
イテレータとはそのような仕組みです。これは、さまざまなデータ構造に対する統合アクセス メカニズムを提供するインターフェイスです。データ構造が Iterator インターフェイスをデプロイしている限り、トラバーサル操作を完了できます (つまり、データ構造のすべてのメンバーを順番に処理します)。
イテレータには 3 つの関数があります。
- 1 つは、さまざまなデータ構造に統一された便利なアクセス インターフェイスを提供することです。
- 2 つ目は、データ構造のメンバーを特定の順序で配置できるようにすることです。
- 3 つ目は、ES6 が新しいトラバーサル コマンド for…of ループを作成し、Iterator インターフェイスが主に for…of の消費に使用されていることです。
2.動作原理
-
現在のデータ構造の先頭を指すポインター オブジェクトを作成します。言い換えれば、トラバーサー オブジェクトは本質的にはポインター オブジェクトです。
-
next
ポインター オブジェクトのメソッドが初めて呼び出されるとき、ポインターはデータ構造の最初のメンバーを指すことができます。 -
ポインター オブジェクトのメソッドが 2 回目に呼び出されるとき
next
、ポインターはデータ構造の 2 番目のメンバーを指します。 -
next
データ構造の終わりを指すまで、ポインター オブジェクトのメソッドを呼び出し続けます。
メソッドが呼び出されるたびに、と2 つのプロパティを含むオブジェクトnext
が返されます。このうち、属性は現在のメンバーの値であり、属性は走査が終了したかどうかを示すブール値です。value
done
value
done
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{
value: array[nextIndex++], done: false} :
{
value: undefined, done: true};
}
};
}
3. デフォルトの Iterator インターフェース
データ構造が Iterator インターフェイスを展開する限り、このデータ構造を「トラバース可能」(反復可能) と呼びます。
ES6 では、デフォルトの Iterator インターフェイスがSymbol.iterator
データ構造のプロパティにデプロイされること、つまり、データ構造にプロパティがある限り、Symbol.iterator
それは「反復可能」であると見なすことができると規定しています。
Symbol.iterator
属性自体は関数であり、現在のデータ構造のデフォルトのトラバーサー生成関数です。この関数を実行するとイテレータが返されます。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
ES6 は新しいトラバーサル コマンドfor…of
ループを作成します。Iterator インターフェイスは主にfor…of
使用されます。
イテレータインターフェイスを備えたネイティブデータ (トラバース可能for...of
)
- 配列
- 地図
- 設定
- 弦
- 型付き配列
- 関数の引数オブジェクト
- NodeList オブジェクト
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
4. Iteratorインターフェースを呼び出す場合
場合によっては、Iterator インターフェイス (つまり、メソッド) がデフォルトで呼び出されます。Symbol.iterator
以下で説明するfor...of
ループに加えて、他にもいくつかの場合があります。
(1) 代入の分割
Symbol.iterator メソッドは、配列と Set 構造体を構造化して代入するときに、デフォルトで呼び出されます。
let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
(2) スプレッド演算子
スプレッド演算子 (…) もデフォルトの Iterator インターフェイスを呼び出します。
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
(3) 収量*
yield*
続いてトラバース可能な構造体が続き、その構造体のトラバーサー インターフェイスが呼び出されます。
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
(4) その他の機会
配列のトラバーサルはトラバーサー インターフェイスを呼び出すため、配列をパラメーターとして受け入れる場合は実際にトラバーサー インターフェイスを呼び出します。以下にいくつかの例を示します。
- の
- Array.from()
- Map()、Set()、WeakMap()、WeakSet() (例
new Map([['a',1],['b',2]])
) - Promise.all()
- Promise.race()
2. for...of ループ
1. アレイ
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
for...of
ループは、配列インスタンスのメソッドの代わりに使用できますforEach
。
const arr = ['red', 'green', 'blue'];
arr.forEach(function (element, index) {
console.log(element); // red green blue
console.log(index); // 0 1 2
});
JavaScript本来のfor...in
ループはオブジェクトのキー名を取得することしかできず、キー値を直接取得することはできません。ES6 はfor...of
ループを提供し、トラバーサルによるキー値の取得を可能にします。
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}
for...of
ループは反復子インターフェイスを呼び出し、配列の反復子インターフェイスは数値インデックスを持つプロパティのみを返します。これはfor...in
サイクルとは異なります。
let arr = [3, 5, 7];
arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
上記のコードでは、for...of
ループは配列 arr の foo プロパティを返しません。
2. 構造の設定とマップ
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e);
}
// Gecko
// Trident
// Webkit
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262
3. 配列状のオブジェクト
配列のようなオブジェクトには、いくつかのクラスが含まれます。以下は、文字列、DOM NodeList オブジェクト、および引数オブジェクトの for...of ループの例です。
// 字符串
let str = "hello";
for (let s of str) {
console.log(s); // h e l l o
}
// DOM NodeList对象
let paras = document.querySelectorAll("p");
for (let p of paras) {
p.classList.add("test");
}
// arguments对象
function printArgs() {
for (let x of arguments) {
console.log(x);
}
}
printArgs('a', 'b');
// 'a'
// 'b'
すべての配列のようなオブジェクトに Iterator インターフェイスがあるわけではありません。簡単な解決策は、Array.from メソッドを使用して配列に変換することです。
let arrayLike = {
length: 2, 0: 'a', 1: 'b' };
// 报错
for (let x of arrayLike) {
console.log(x);
}
// 正确
for (let x of Array.from(arrayLike)) {
console.log(x);
}
4. オブジェクト
通常のオブジェクトの場合、for...of
構造体を直接使用することはできず、エラーが報告されます。また、使用する前に Iterator インターフェイスをデプロイする必要があります。ただし、この場合でも、for...in
ループを使用してキー名を走査することができます。
let es6 = {
edition: 6,
committee: "TC39",
standard: "ECMA-262"
};
for (let e in es6) {
console.log(e);
}
// edition
// committee
// standard
for (let e of es6) {
console.log(e);
}
// TypeError: es6[Symbol.iterator] is not a function
上記のコードは、通常のオブジェクトの場合、for...in
ループがキー名を走査でき、for...of
ループがエラーを報告することを示しています。
1 つの解決策は、メソッドを使用してObject.keys
オブジェクトのキー名の配列を生成し、その配列を走査することです。
for (var key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key]);
}
もう 1 つの方法は、Generator 関数を使用してオブジェクトを再パックすることです。
const obj = {
a: 1, b: 2, c: 3 }
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3
3. 他のトラバーサル構文との比較
配列を例に挙げると、JavaScript はさまざまなトラバーサル構文を提供します。最も原始的な記述方法は for ループです。
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
この書き方は面倒なので、配列には組み込みforEach
メソッドが用意されています。
myArray.forEach(function (value) {
console.log(value);
});
forEach
この書き方の問題は、ループから抜け出す方法がなく、break
コマンドもreturn
コマンドも機能しないことです。
for...in
ループは配列のキーを反復処理できます。
for (var index in myArray) {
console.log(myArray[index]);
}
for...in
ループにはいくつかの欠点があります。
- 配列のキーは数値ですが、
for...in
ループは弦キー名としては「0」、「1」、「2」などです。 for...in
このループは数値キー名を反復処理するだけでなく、手動で追加された他のキー (プロトタイプ チェーン上のキーも含む) も反復処理します。- 場合によっては、for...in ループが任意の順序でキーを反復処理します。
つまり、for...in
このサイクルは主に次のようなものです。オブジェクトを横断する設計上、配列の走査には適していません。
for...of
上記のいくつかのアプローチと比較して、ループにはいくつかの重要な利点があります。
for (let value of myArray) {
console.log(value);
}
- 同じ簡潔な構文を持ちます
for...in
が、for...in
それらの欠点はありません。 - メソッドとは異なり
forEach
、break、continue和return
で使用できます。 - すべてのデータ構造を横断するための統合された操作インターフェイスを提供します。
以下は、ステートメントを使用してループbreak
を抜け出す例です。for...of
for (var n of fibonacci) {
if (n > 1000)
break;
console.log(n);
}
上記の例では、フィボナッチ数列が 1000 以下の項目を出力します。現在の項目が 1000 より大きい場合、ループをbreak
抜け出すためにステートメントが使用されます。for...of