イテレータと for...of ループ

イテレータの概念

JavaScript 本来の「集合」を表すデータ構造は主に配列 (Array) とオブジェクト (Object) ですが、ES6 では MapSet が追加されました。このように、4 つのデータ コレクションがあり、ユーザーはそれらを組み合わせて独自のデータ構造を定義することもできます。たとえば、配列のメンバーは Map であり、Map のメンバーはオブジェクトです。これには、すべての異なるデータ構造を処理するための統一されたインターフェイス メカニズムが必要です。

イテレータとはそのような仕組みです。これは、さまざまなデータ構造に統一されたアクセス メカニズムを提供するインターフェイスです。データ構造が Iterator インターフェイスをデプロイしている限り、トラバーサル操作を完了できます (つまり、データ構造のすべてのメンバーを順番に処理します)。

Iterator には 3 つの機能があります。1 つ目は、さまざまなデータ構造に統一されたシンプルなアクセス インターフェイスを提供することです。2 つ目は、データ構造のメンバーを特定の順序で配置できるようにすることです。3 つ目は、ES6 が新しいトラバーサル コマンドを作成したことです。 .ofループでは、Iterator インターフェイスは主にfor...of` の使用に使用されます。

Iteratorのトラバース処理はこんな感じです。

(1) 現在のデータ構造の開始位置を指すポインタ オブジェクトを作成します。言い換えれば、トラバーサー オブジェクトは本質的にはポインター オブジェクトです。

(2) ポインタ オブジェクトの next メソッドを初めて呼び出すと、ポインタをデータ構造の最初のメンバーに指すことができます。

(3) ポインター オブジェクトの「next」メソッドが 2 回目に呼び出されるとき、ポインターはデータ構造の 2 番目のメンバーを指します。

(4) データ構造の終わりを指すまで、ポインタ オブジェクトの next メソッドを呼び出し続けます。

「next」メソッドが呼び出されるたびに、データ構造の現在のメンバーに関する情報が返されます。具体的には、「value」と「done」という 2 つのプロパティを含むオブジェクトを返します。このうち、「value」属性は現在のメンバーの値であり、「done」属性はトラバーサルが終了したかどうかを示すブール値です。

以下は、「next」メソッドの戻り値をシミュレートする例です。

var it = makeIterator(['a', 'b']);

it.next() // { 値: "a"、完了: false }
it.next() // { 値: "b"、完了: false }
it.next() // { 値: 未定義、完了: true }

関数 makeIterator(array) {
  var nextIndex = 0;
  戻る {
    次へ: function() {
      nextIndex < array.length を返しますか?
        {値: array[nextIndex++]、完了: false} :
        {値: 未定義、完了: true};
    }
  };
}

上記のコードは、トラバーサ生成関数である makeIterator 関数を定義しており、その機能はトラバーサ オブジェクトを返すことです。配列 ['a', 'b'] に対してこの関数を実行すると、配列のイテレータ オブジェクト (つまりポインタ オブジェクト) it が返されます。

ポインタオブジェクトの「next」メソッドは、ポインタを移動するために使用されます。最初、ポインタは配列の先頭を指します。その後、「next」メソッドが呼び出されるたびに、ポインタは配列の次のメンバーを指します。最初の呼び出しは「a」を指し、2 番目の呼び出しは「b」を指します。

「next」メソッドは、現在のデータ メンバーの情報を表すオブジェクトを返します。このオブジェクトには、「value」と「done」の 2 つの属性があります。「value」属性は、現在の位置のメンバーを返します。「done」属性は、トラバーサルが終了したかどうか、つまり終了したかどうかを示します。 next メソッドを再度呼び出す必要があります。

つまり、ポインタオブジェクトの「next」メソッドを呼び出すことで、あらかじめ与えられたデータ構造をたどることができます。

iterator オブジェクトの場合、done: false および value: unknown 属性は省略できるため、上記の makeIterator 関数は次の形式に短縮できます。

関数 makeIterator(array) {
  var nextIndex = 0;
  戻る {
    次へ: function() {
      nextIndex < array.length を返しますか?
        {値: array[nextIndex++]} :
        {完了: true};
    }
  };
}

Iterator はインターフェイス仕様をデータ構造に追加するだけであるため、イテレータとそれがトラバースするデータ構造は実際には分離されています。対応するデータ構造なしでトラバーサ オブジェクトを作成したり、データ構造をシミュレートするトラバーサ オブジェクトを使用したりすることは完全に可能です。以下は、無限に実行されるトラバーサー オブジェクトの例です。

var it = idMaker();

it.next().value // 0
it.next().value // 1
it.next().value // 2
// ...

関数 idMaker() {
  変数インデックス = 0;

  戻る {
    次へ: function() {
      return {値:index++、完了:false};
    }
  };
}

上記の例では、トラバーサーは、トラバーサー オブジェクト (つまり、ポインター オブジェクト) を返す関数 idMaker を生成します。しかし、対応するデータ構造はありません。つまり、トラバーサー オブジェクト自体がデータ構造を記述します。

TypeScriptで記述する場合、イテレータインタフェース(Iterable)、ポインタオブジェクト(Iterator)、nextメソッドの戻り値の仕様は以下のように記述できます。

インターフェース Iterable {
  [Symbol.iterator]() : イテレータ、
}

インターフェイス イテレータ {
  next(value?: any) : IterationResult、
}

インターフェース IterationResult {
  値: 任意、
  完了: ブール値、
}

##デフォルトの反復子インターフェース

Iterator インターフェイスの目的は、すべてのデータ構造に対する統合アクセス メカニズム、つまり for...of ループを提供することです (詳細については以下を参照)。 for...of ループを使用して特定のデータ構造を走査すると、ループは自動的に Iterator インターフェイスを探します。

データ構造が Iterator インターフェイスを展開する限り、このデータ構造を「トラバース可能」(反復可能) と呼びます。

ES6 では、デフォルトの Iterator インターフェイスがデータ構造の Symbol.iterator プロパティに展開されることが規定されています。つまり、データ構造が Symbol.iterator プロパティを持つ限り、それは「トラバース可能」(反復可能) であると見なされます。 )。 Symbol.iterator プロパティ自体は関数であり、現在のデータ構造のデフォルトの反復子生成関数です。この関数を実行すると、トラバーサーが返されます。プロパティ名 Symbol.iterator に関しては、これは Symbol オブジェクトの iterator プロパティを返す式です。これは Symbol 型の事前定義された特別な値であるため、角括弧内に配置する必要があります (章を参照)。 "シンボル")。

const obj = {
  [Symbol.iterator] : function () {
    戻る {
      次へ: function () {
        戻る {
          値: 1、
          完了: true
        };
      }
    };
  }
};

上記のコードでは、オブジェクト objSymbol.iterator プロパティを持つため反復可能 (反復可能) です。このプロパティを実行すると、トラバーサー オブジェクトが返されます。このオブジェクトの基本的な特徴は、「next」メソッドがあることです。 「next」メソッドが呼び出されるたびに、現在のメンバーを表す情報オブジェクトが「value」と「done」の 2 つのプロパティとともに返されます。

ES6 の一部のデータ構造にはネイティブの Iterator インターフェイス (配列など) があります。つまり、処理を行わずに for...of ループで走査できます。その理由は、これらのデータ構造は Symbol.iterator プロパティ (詳細については以下を参照) を使用してネイティブに展開されるのに対し、他のデータ構造 (オブジェクトなど) はそうでないためです。 Symbol.iterator 属性を展開するデータ構造は、イテレータ インターフェイスを展開していると言われます。このインターフェイスを呼び出すと、トラバーサー オブジェクトが返されます。

Iterator インターフェイスを使用したネイティブ データ構造は次のとおりです。

-配列 -地図 -セット -弦 -TypedArray

  • 関数の引数オブ​​ジェクト
  • NodeList オブジェクト

次の例は、配列の Symbol.iterator プロパティです。

arr = ['a', 'b', 'c']; とします。
let iter = arr[Symbol.iterator]();

iter.next() // { 値: 'a'、完了: false }
iter.next() // { 値: 'b'、完了: false }
iter.next() // { 値: 'c'、完了: false }
iter.next() // { 値: 未定義、完了: true }

上記のコードでは、変数 arr は配列であり、ネイティブ イテレータ インターフェイスを持ち、arrSymbol.iterator プロパティにデプロイされます。したがって、このプロパティを呼び出すと、traverser オブジェクトが取得されます。

Iterator インターフェイスをネイティブに展開するデータ構造の場合、トラバーサー生成関数を自分で記述する必要はなく、for...of ループが自動的にデータ構造を走査します。さらに、他のデータ構造 (主にオブジェクト) の Iterator インターフェイスを Symbol.iterator プロパティにデプロイして、for...of ループで走査できるようにする必要があります。

オブジェクトがデフォルトで Iterator インターフェースをデプロイしない理由は、オブジェクトのどのプロパティが最初に走査され、どのプロパティが後で走査されるかが不明であり、開発者が手動で指定する必要があるためです。本質的に、トラバーサーは線形プロセスです。非線形データ構造の場合、トラバーサー インターフェイスの展開は線形変換の展開と同じです。ただし、厳密に言えば、現時点ではオブジェクトは Map 構造として実際に使用されるため、ES5 には Map 構造がネイティブで提供されているため、オブジェクトのトラバーサー インターフェイスをデプロイする必要はありません。

オブジェクトが for...of ループで呼び出せる Iterator インターフェイスを持ちたい場合は、Symbol.iterator のプロパティにトラバーサー生成メソッドをデプロイする必要があります (プロトタイプ チェーン上のオブジェクトは、この方法です)。

クラス RangeIterator {
  コンストラクター(開始、停止) {
    this.value = 開始;
    this.stop = 停止;
  }

  [Symbol.iterator]() { これを返す }

  次() {
    var 値 = this.value;
    if (値 < this.stop) {
      this.value++;
      return {完了: false、値: 値};
    }
    return {完了: true、値: 未定義};
  }
}

関数範囲(開始、停止) {
  新しい RangeIterator(start, stop) を返します。
}

for (range(0, 3) の var 値) {
  console.log(値); // 0、1、2
}

上記のコードは、Iterator インターフェイスをデプロイするクラスを作成する方法です。 Symbol.iterator プロパティは、実行後に現在のオブジェクトのイテレータ オブジェクトを返す関数に対応します。

以下は、トラバーサを介して「リンク リスト」構造を実装する例です。

関数 Obj(値) {
  this.value = 値;
  this.next = null;
}

Obj.prototype[Symbol.iterator] = function() {
  var iterator = { 次: 次 };

  var current = this;

  関数 next() {
    if (現在) {
      var 値 = 現在の値;
      現在 = 現在.次;
      return { 完了: false、値: 値 };
    }
    戻り値 { 完了: true };
  }
  反復子を返します。
}

var one = 新しいオブジェクト(1);
var two = 新しい Obj(2);
var 3 = 新しい Obj(3);

1.次 = 2;
2.次 = 3;

for (var i of one){
  コンソール.log(i); // 1、2、3
}

上記のコードは、まずコンストラクターのプロトタイプ チェーンに Symbol.iterator メソッドをデプロイし、このメソッドを呼び出すと反復子オブジェクト iterator が返され、値を返しながらオブジェクトの next メソッドが呼び出されます。ポインタは自動的に次のインスタンスに移動します。

次に、Iterator インターフェイスをオブジェクトに追加する別の例を示します。

obj = { にします
  データ: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    インデックス = 0 とします。
    戻る {
      次() {
        if (インデックス < self.data.length) {
          戻る {
            値: self.data[index++]、
            完了: false
          };
        }
        return { 値: 未定義、完了: true };
      }
    };
  }
};

配列のようなオブジェクト (数値キー名と length プロパティを持つ) の場合、Iterator インターフェースを展開する便利な方法があります。これは、配列の Iterator インターフェースの Symbol.iterator メソッドを直接参照することです。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// または
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

[...document.querySelectorAll('div')] // 実行準備完了

NodeList オブジェクトは、本質的にトラバーサル インターフェイスを持ち、直接トラバースできる配列のようなオブジェクトです。上記のコードでは、トラバーサル インターフェイスを配列の Symbol.iterator プロパティに変更しましたが、効果がないことがわかります。

以下は、配列の Symbol.iterator メソッドを呼び出す配列のようなオブジェクトの別の例です。

let iterable = {
  0: 'a'1: 「b」、
  2: 「c」、
  長さ: 3、
  [シンボル.イテレータ]: 配列.プロトタイプ[シンボル.イテレータ]
};
for (反復可能な項目を許可) {
  console.log(item); // 'a'、'b'、'c'
}

配列を展開する通常のオブジェクトの Symbol.iterator メソッドは効果がないことに注意してください。

let iterable = {
  a:「あ」、
  b: 「b」、
  c: 「c」、
  長さ: 3、
  [シンボル.イテレータ]: 配列.プロトタイプ[シンボル.イテレータ]
};
for (反復可能な項目を許可) {
  console.log(item); // 未定義、未定義、未定義
}

Symbol.iterator メソッドがイテレータ生成関数に対応しない (つまり、イテレータ オブジェクトを返す) 場合、解釈エンジンはエラーを報告します。

var obj = {};

obj[Symbol.iterator] = () => 1;

[...obj] // TypeError: [] は関数ではありません

上記のコードでは、変数 objSymbol.iterator メソッドがトラバーサ生成関数に対応していないため、エラーが報告されます。

トラバーサー インターフェイスを使用すると、「for...of」ループ (詳細は以下を参照) または「while」ループを使用してデータ構造をトラバースできます。

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

上記のコードでは、ITERABLE はある種の走査可能なデータ構造を表し、$iterator はその反復子オブジェクトです。トラバーサ オブジェクトはポインタを移動するたびに (「next」 メソッド)、戻り値の done 属性をチェックします。トラバーサルが終了していない場合は、トラバーサ オブジェクトのポインタを次のステップに移動します (「next」 メソッド)。 )、ループが継続します。

Iteratorインターフェースを呼び出す場合

以下で紹介する for...of ループに加えて、Iterator インターフェイス (つまり Symbol.iterator メソッド) がデフォルトで呼び出される場合があります。

(1) 代入の構造化

配列や Set 構造体を構造化して値を代入する場合、デフォルトで Symbol.iterator メソッドが呼び出されます。

let set = new Set().add('a').add('b').add('c');

[x,y] = 設定してみましょう。
// x='a'; y='b';

let [first, ...rest] = セット;
// 最初='a'; 残り=['b','c'];

(2)拡張演算子

スプレッド演算子 (...) も、デフォルトの Iterator インターフェイスを呼び出します。

//例1
var str = 'こんにちは';
[...str] // ['h','e','l','l','o']

//例2
arr = ['b', 'c']; とします。
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

上記のコードのスプレッド演算子は、内部で Iterator インターフェイスを呼び出します。

実際、これは、Iterator インターフェイスを実装するデータ構造を配列に変換する便利なメカニズムを提供します。つまり、データ構造が Iterator インターフェイスをデプロイしている限り、スプレッド演算子を使用して配列に変換できます。

let arr = [...反復可能];

(3)収量*

yield* の後には、構造のトラバーサー インターフェイスを呼び出すトラバース可能な構造が続きます。

let ジェネレーター = function* () {
  収量1;
  収量* [2,3,4];
  収量5;
};

var iterator = ジェネレーター();

iterator.next() // { 値: 1、完了: false }
iterator.next() // { 値: 2、完了: false }
iterator.next() // { 値: 3、完了: false }
iterator.next() // { 値: 4、完了: false }
iterator.next() // { 値: 5、完了: false }
iterator.next() // { 値: 未定義、完了: true }

(4) その他の機会

配列トラバーサルはトラバーサー インターフェイスを呼び出すため、配列をパラメーターとして受け入れる場合は実際にトラバーサー インターフェイスを呼び出します。以下にいくつかの例を示します。

-のために...の -Array.from()

  • Map()、Set()、WeakMap()、WeakSet() (new Map([['a',1],['b',2]]) など)
  • Promise.all()
  • Promise.race()

文字列の反復子インターフェース

文字列は配列のようなオブジェクトであり、ネイティブに Iterator インターフェイスも備えています。

var someString = "こんにちは";
someString の型[Symbol.iterator]
// "関数"

var iterator = someString[Symbol.iterator]();

iterator.next() // { 値: "h"、完了: false }
iterator.next() // { 値: "i"、完了: false }
iterator.next() // { 値: 未定義、完了: true }

上記のコードでは、「Symbol.iterator」メソッドを呼び出すとトラバーサー オブジェクトが返され、そのオブジェクトに対して次のメソッドを呼び出して文字列トラバーサルを実装できます。

ネイティブの Symbol.iterator メソッドをオーバーライドしてイテレータの動作を変更できます。

var str = 新しい文字列("hi");

[...str] // ["h", "i"]

str[Symbol.iterator] = function() {
  戻る {
    次へ: function() {
      if (this._first) {
        this._first = false;
        return { 値: "さようなら"、完了: false };
      } それ以外 {
        戻り値 { 完了: true };
      }
    }、
    _first: true
  };
};

[...str] // ["さようなら"]
str // "こんにちは"

上記のコードでは、文字列 str の Symbol.iterator メソッドが変更されているため、文字列自体は hi のままですが、スプレッド演算子 (...) によって返される値は bye になります。

Iterator インターフェースと Generator 関数

Symbol.iterator() メソッドの最も簡単な実装は、次の章で紹介する Generator 関数を使用することです。

let myIterable = {
  [Symbol.iterator]: function* () {
    収量1;
    収量2;
    収量3;
  }
};
[...myIterable] // [1, 2, 3]

// または、次のような簡潔な記述方法を使用します

obj = { にします
  * [Symbol.iterator]() {
    「こんにちは」を返します。
    「世界」を生成します。
  }
};

for (obj の x を指定) {
  コンソール.ログ(x);
}
// "こんにちは"
// "世界"

上記のコードでは、yield コマンドを使用して各ステップの戻り値を指定する限り、Symbol.iterator() メソッドをデプロイするコードはほとんど必要ありません。

トラバーサー オブジェクトの return()、throw()

next() メソッドに加えて、トラバーサ オブジェクトは return() メソッドと throw() メソッドを持つこともできます。トラバーサー オブジェクト生成関数を自分で作成する場合は、next() メソッドをデプロイする必要があります。また、return() メソッドと throw() メソッドをデプロイするかどうかはオプションです。

return() メソッドは、for...of ループが早期に終了する場合 (通常はエラーまたは break ステートメントが原因) に使用され、return() メソッドが呼び出されます。トラバーサルを完了する前にオブジェクトがリソースをクリーンアップまたは解放する必要がある場合は、return() メソッドをデプロイできます。

関数 readLinesSync(file) {
  戻る {
    [Symbol.iterator]() {
      戻る {
        次() {
          return { 完了: false };
        }、
        戻る() {
          file.close();
          戻り値 { 完了: true };
        }
      };
    }、
  };
}

上記のコードでは、関数 readLinesSync はパラメータとしてファイル オブジェクトを受け取り、next() メソッドに加えて、traverser オブジェクトを返します。また、return() メソッドもデプロイされています。次の 2 つの状況では、return() メソッドの実行がトリガーされます。

// 状況 1
for (readLinesSync(fileName) の行を許可) {
  コンソール.ログ(行);
  壊す;
}

// ケース 2
for (readLinesSync(fileName) の行を許可) {
  コンソール.ログ(行);
  新しい Error() をスローします。
}

上記のコードでは、ケース 1 ではファイルの最初の行が出力された後、return() メソッドが実行されてファイルが閉じられます。ケース 2 では、return() メソッドの後にエラーがスローされます。ファイルを閉じるために実行されます。

return() メソッドは、ジェネレーター構文によって決定されるオブジェクトを返さなければならないことに注意してください。

throw() メソッドは主に Generator 関数と組み合わせて使用​​されます。このメソッドは一般的なトラバーサー オブジェクトでは使用されません。 「ジェネレータ関数」の章を参照してください。

for...of ループ

ES6 は C++、Java、C#、Python 言語を借用し、すべてのデータ構造を横断するための統一された方法として for...of ループを導入しています。

データ構造が Symbol.iterator 属性を使用して展開されている限り、その構造はイテレータ インターフェイスを持つとみなされ、そのメンバーは for...of を使用して走査できます。つまり、「for...of」ループ内で呼び出されるのは、データ構造の「Symbol.iterator」メソッドです。

「for...of」ループの使用範囲には、配列、Set および Map 構造、特定の配列のようなオブジェクト (「arguments」オブジェクト、DOM NodeList オブジェクトなど)、次の Generator オブジェクト、および文字列が含まれます。

配列

配列にはネイティブに iterator インターフェイスがあります (つまり、Symbol.iterator 属性がデフォルトでデプロイされます) for...of ループは本質的にこのインターフェイスを呼び出すことによって生成されるトラバーサであり、これは次の式で証明できます。次のコード。

const arr = ['赤', '緑', '青'];

for(arr の v を許可) {
  console.log(v); // 赤、緑、青
}

const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);

for(obj の v を許可) {
  console.log(v); // 赤、緑、青
}

上記のコードでは、空のオブジェクト obj は配列 arrSymbol.iterator プロパティを展開します。その結果、 objfor...of ループは ` とまったく同じ結果を生成します。ああ。

「for...of」ループは、配列インスタンスの「forEach」メソッドを置き換えることができます。

const arr = ['赤', '緑', '青'];

arr.forEach(関数 (要素, インデックス) {
  console.log(要素); // 赤、緑、青
  コンソール.log(インデックス); // 0 1 2
});

JavaScript 本来の for...in ループはオブジェクトのキー名を取得することしかできず、キー値を直接取得することはできません。 ES6 は「for...of」ループを提供し、反復でキー値を取得できるようにします。

var arr = ['a', 'b', 'c', 'd'];

for (arr に a を入れます) {
  コンソール.log(a); // 0 1 2 3
}

for (arr の a を許可) {
  console.log(a); // a b c d
}

上記のコードは、「for...in」ループでキー名を読み取り、「for...of」ループでキー値を読み取ることを示しています。 for...of ループを通じて配列のインデックスを取得したい場合は、配列インスタンスの entries メソッドと keys メソッドを使用できます (「配列の拡張」の章を参照)。

for...of ループはトラバーサー インターフェイスを呼び出します。配列のトラバーサー インターフェイスは数値インデックスを持つプロパティのみを返します。これは、「for...in」ループとも異なります。

arr = [3, 5, 7] とします。
arr.foo = 'こんにちは';

for (arr に入力させてください) {
  console.log(i); // "0"、"1"、"2"、"foo"
}

for (arr の i を許可) {
  console.log(i); // "3"、"5"、"7"
}

上記のコードでは、for...of ループは配列 arrfoo 属性を返しません。

構造の設定とマップ

Set 構造と Map 構造にもネイティブ Iterator インターフェイスがあり、「for...of」 ループを直接使用できます。

var エンジン = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (エンジンの種類) {
  コンソール.ログ(e);
}
// ヤモリ
//トライデント
// ウェブキット

var es6 = 新しい Map();
es6.set("エディション", 6);
es6.set("委員会", "TC39");
es6.set("標準", "ECMA-262");
for (es6 の var [名前, 値]) {
  console.log(名前 + ": " + 値);
}
// エディション: 6
// 委員会: TC39
// 標準: ECMA-262

上記のコードは、Set 構造と Map 構造をトラバースする方法を示しています。注目すべき点が 2 つあります。まず、走査の順序は、各メンバーがデータ構造に追加される順序です。次に、Set 構造をトラバースすると値が返され、Map 構造をトラバースすると配列が返されます。配列の 2 つのメンバーは、現在の Map メンバーのキー名とキー値です。

let map = new Map().set('a', 1).set('b', 2);
for (マップのペアを許可) {
  コンソール.ログ(ペア);
}
// ['a', 1]
// ['b', 2]

for (マップの [キー, 値] にします) {
  console.log(キー + ' : ' + 値);
}
// a : 1
// b : 2

計算により生成されるデータ構造

一部のデータ構造は、既存のデータ構造に基づいて計算および生成されます。たとえば、ES6 の配列、Set、Map はすべて次の 3 つのメソッドをデプロイしており、これらはすべて呼び出された後にトラバーサー オブジェクトを返します。

  • entries() は、[キー名、キー値] で構成される配列を走査するために使用されるトラバーサー オブジェクトを返します。配列の場合、キー名は Set のインデックス値であり、キー名はキー値と同じです。 Map 構造の Iterator インターフェイスは、デフォルトで entries メソッドを呼び出します。
  • keys() は、すべてのキー名を走査するために使用されるトラバーサー オブジェクトを返します。
  • values() は、すべてのキー値を走査するために使用されるトラバーサー オブジェクトを返します。

これら 3 つのメソッドを呼び出した後に生成されたトラバーサー オブジェクトは、計算によって生成されたデータ構造を走査します。

arr = ['a', 'b', 'c']; とします。
for (arr.entries() のペアを許可) {
  コンソール.ログ(ペア);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']

配列のようなオブジェクト

配列のようなオブジェクトにはいくつかのカテゴリがあります。以下は、文字列、DOM NodeList オブジェクト、および arguments オブジェクトで使用される for...of ループの例です。

// 弦
let str = "こんにちは";

for (str の s) {
  console.log(s); // h e l l o
}

// DOM NodeList オブジェクト
let paras = document.querySelectorAll("p");

for (パラスを p にします) {
  p.classList.add("テスト");
}

//引数オブジェクト
関数 printArgs() {
  for (引数を x にします) {
    コンソール.ログ(x);
  }
}
printArgs('a', 'b');
// 'あ'
// 'b'

文字列の場合、「for...of」ループのもう 1 つの特徴は、32 ビット UTF-16 文字を正しく認識することです。

for (x を 'a\uD83D\uDC0A') {
  コンソール.ログ(x);
}
// 'あ'
// '\uD83D\uDC0A'

すべての配列のようなオブジェクトに Iterator インターフェイスがあるわけではありません。簡単な解決策は、Array.from メソッドを使用して配列に変換することです。

let arrayLike = { 長さ: 2, 0: 'a', 1: 'b' };

// エラーを報告する
for (arrayLike の x を許可) {
  コンソール.ログ(x);
}

// 正しい
for (let x of Array.from(arrayLike)) {
  コンソール.ログ(x);
}

物体

通常のオブジェクトの場合、「for...of」構造体を直接使用することはできないため、使用する前に Iterator インターフェースをデプロイする必要があります。ただし、この場合でも、「for...in」ループを使用してキー名を走査することができます。

let es6 = {
  エディション: 6、
  委員会:「TC39」、
  規格:「ECMA-262」
};

for (es6 に e を入れます) {
  コンソール.ログ(e);
}
// エディション
// 委員会
// 標準

for (let e of es6) {
  コンソール.ログ(e);
}
// TypeError: es6[Symbol.iterator] は関数ではありません

上記のコードは、通常のオブジェクトの場合、「for...in」ループはキー名を走査でき、「for...of」ループはエラーを報告することを示しています。

1 つの解決策は、「Object.keys」メソッドを使用してオブジェクトのキー名の配列を生成し、その配列を反復処理することです。

for (Object.keys(someObject) の var key) {
  console.log(key + ': ' + someObject[key]);
}

もう 1 つの方法は、ジェネレーター関数を使用してオブジェクトを再パッケージ化することです。

const obj = { a: 1b: 2c: 3 }

function* エントリ(obj) {
  for (Object.keys(obj) のキーを許可) {
    yield [キー, obj[キー]];
  }
}

for (let [キー, 値] of events(obj)) {
  console.log(キー, '->', 値);
}
// a -> 1
// b -> 2
// c -> 3

他のトラバーサル構文との比較

配列を例に挙げると、JavaScript は複数のトラバーサル構文を提供します。最も原始的な記述方法は for ループです。

for (var インデックス = 0; インデックス < myArray.length; インデックス++) {
  console.log(myArray[インデックス]);
}

この書き方は面倒なので、配列には組み込みの forEach メソッドが用意されています。

myArray.forEach(関数 (値) {
  console.log(値);
});

この書き方の問題点は、forEach ループを途中で抜け出すことができず、break コマンドも return コマンドも機能しないことです。

「for...in」ループは配列のキーを反復処理できます。

for (myArray の変数インデックス) {
  console.log(myArray[インデックス]);
}

「for...in」ループにはいくつかの欠点があります。

  • 配列のキーは数値ですが、「for...in」ループではキー「0」、「1」、「2」などとして文字列が使用されます。
  • for...in ループは数値キー名を走査するだけでなく、手動で追加された他のキー (プロトタイプ チェーン上のキーも含む) も走査します。
  • 場合によっては、「for...in」ループが任意の順序でキーを反復処理します。

つまり、「for...in」ループは主にオブジェクトを走査するために設計されており、配列の走査には適していません。

for...of ループには、上記の方法と比較していくつかの重要な利点があります。

for (myArray の値を許可) {
  console.log(値);
}
  • for...in と同じ簡潔な構文を持ちますが、for...in の欠点はありません。
  • forEachメソッドとは異なり、breakContinuereturnとともに使用できます。
  • すべてのデータ構造を横断するための統一された操作インターフェイスを提供します。

以下は、break ステートメントを使用して for...of ループから抜け出す例です。

for (フィボナッチの変数 n) {
  if (n > 1000)
    壊す;
  コンソール.log(n);
}

上記の例では、フィボナッチ数列内の 1000 以下の項目が出力されます。現在の項目が 1000 より大きい場合、break ステートメントを使用して for...of ループから抜け出します。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0