イテレータ
イテレーターは、データを消費して一度に 1 ステップずつ動作を制御するための、順序付けされたシーケンシャルなプルベースの組織です。
簡単に言うと、反復可能なオブジェクトを繰り返してループし、すべてのデータを一度に返すのではなく、関連するメソッドを呼び出してバッチで返します。
イテレータは、特定のデータ構造をトラバースするのに役立つオブジェクトです。これには、反復シーケンス内の現在の関数によって定義された値を指す属性を持つ値を返す関数がobject
含まれています。next
value
done
object
value
next
{
done: boolean, // 为 true 时代表迭代完毕
value: any // done 为 true 时取值为 undefined
}
反復プロトコル
ES6 のイテレーション プロトコルはイテレータ プロトコル (イテレータ プロトコル) とイテラブル プロトコル (イテラブル プロトコル)に分かれており、イテレータはこの 2 つのプロトコルに基づいて実装されています。
Iterator Protocol: シーケンスを生成する標準的な方法iterator协议
を定義します。value
このオブジェクトは、必要な関数を実装している限りnext
イテレータです。データベースのカーソルに似た、データ構造の要素を横断するための非常にポインタです。
反復可能プロトコル:反復可能プロトコルがサポートされると、オブジェクトをトラバーサルfor-of
に使用でき。Array
&などの一般的な組み込み型は、Map
反復可能なプロトコルをサポートしています。オブジェクトは@@iterator
メソッドを実装する必要があります。つまり、オブジェクトにはアクセス@@iterator key
可能な定数を持つプロパティが必要ですSymbol.iterator
。
イテレータの実装をシミュレートする
イテレータプロトコルに基づく
// 实现
function createArrayIterator(arr) {
let index = 0
return {
next: () =>
index < arr.length
? {
value: arr[index++], done: false }
: {
value: undefined, done: true },
}
}
// 测试
const nums = [11, 22, 33, 44]
const numsIterator = createArrayIterator(nums)
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
console.log(numsIterator.next())
反復可能なプロトコルに基づく
イテレータを生成するメソッドを実装するオブジェクトが呼び出されます可迭代对象
。つまり、このオブジェクトにはイテレータ オブジェクトを返すメソッドが含まれています。
一般にSymbol.iterator
この属性を定義するために使用され、学名は@@iterator
メソッドと呼ばれます。
// 一个可迭代对象需要具有[Symbol.iterator]方法,并且这个方法返回一个迭代器
const obj = {
names: ['111', '222', '333'],
[Symbol.iterator]() {
let index = 0
return {
next: () =>
index < this.names.length
? {
value: this.names[index++], done: false }
: {
value: undefined, done: true },
return: () => {
console.log('迭代器提前终止了...')
return {
value: undefined, done: true }
},
}
},
}
// 测试
for (const item of obj) {
console.log(item)
if (item === '222') break
}
上記の 2 つの模擬イテレーターの例では、まだ比較的複雑ですが、ES6 ではジェネレーターオブジェクトが導入されており、イテレーター オブジェクトの作成プロセスがはるかに簡単になります。
ビルダー
ジェネレーターは、キーワードの後のアスタリスク (*****) で示される反復子を返す関数であり、新しいキーワードは関数で使用されます。function
yield
// 生成器
function* creatIterator (){
yield 1
yield 2
yield 3
}
const iterator = creatIterator()
console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}
上記の例では、creatIterator()
先頭のアスタリスク * はジェネレータであることを示しており、yield
イテレータ メソッドを呼び出すnext()
ときの戻り値と戻り順序はキーワードによって指定されます。
yield ステートメントが実行されると、関数は自動的に実行を停止します。上記の例を例に挙げると、ステートメントの実行後、関数は他の言語を実行せず、yield 1
反復子メソッドが再度呼び出されるまでステートメントnext()
は続行されませんyield 2
。
注:yield
式はジェネレーター関数でのみ使用でき、他の場所で使用するとエラーが報告されます。
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
function
注: ES6 では、キーワードと関数名の間のアスタリスクをどこに記述するかは規定されていません。これにより、次のような書き込みが可能になります。
function * foo(x, y) {
··· }
function *foo(x, y) {
··· }
function* foo(x, y) {
··· }
function*foo(x, y) {
··· }
ジェネレータパスパラメータ
yield
式自体は値を返さないか、常に を返しますundefined
。このメソッドは、前の式の戻り値next
として使用されるパラメーターを 1 つ受け取ることができます。yield
function* dr(arg) {
console.log(arg)
let one = yield '111'
console.log(one)
yield '222'
console.log('ccc')
}
let iterator = dr('aaa')
console.log(iterator.next())
console.log(iterator.next('bbb'))
console.log(iterator.next())
アプリケーションシナリオ
日常の開発において、次のインターフェイスが前のインターフェイスのデータに依存する場合、ジェネレーターは非同期コールバック地獄のネストの問題を考慮せずに使用できます。
シミュレーション: 1秒後にユーザーデータ、2秒後に注文情報、3秒後に製品情報を取得
function getUser() {
setTimeout(() => {
const data = '用户数据'
iterator.next(data)
}, 1000)
}
function getOrder() {
setTimeout(() => {
const data = '订单信息'
iterator.next(data)
}, 2000)
}
function getGoods() {
setTimeout(() => {
const data = '商品数据'
iterator.next(data)
}, 3000)
}
function* initData() {
const user = yield getUser()
console.log(user)
const order = yield getOrder()
console.log(order)
const goods = yield getGoods()
console.log(goods)
}
const iterator = initData()
iterator.next()
の
for of
このループはイテレータと密接に関連しているため、ループはキー値のペアのキー値を取得できます。そのため、ここでそれについて説明します。
Symbol.iterator
データ構造は R インターフェイスを持つとみなされ、プロパティがデプロイされている限りiterato
使用できfor of
、反復可能なオブジェクトをループできます。
JavaScript
デフォルトのiterable
インターフェースを備えたデータ構造:
- 配列 配列
- 地図
- 設定
- 弦
- 引数オブジェクト
- ノードリスト オブジェクト、クラス配列、および
iterator
インターフェイスを展開するデータ構造では、配列の拡散演算子 (…) や代入の構造化などの操作を使用できます。
配列を反復処理する
for または を使用して配列をループしてみてください
配列はfor...of
ループをサポートしているため、配列にはインターフェイスがデプロイされている必要があります。それを使用しての走査プロセスをIterator
見てみましょう。Iterator
図から次のことがわかります。
Iterator
インターフェイスはnext
メソッドを含むオブジェクトを返します。- next を呼び出すたびに、データ構造の終わりを指すまで、配列内の項目が順番に返されます。
value
返された結果は、現在の値と現在の終了かどうかを含むオブジェクトです。done
オブジェクトを横断する
オブジェクトを走査してみると、以下に示すように、オブジェクトが反復可能ではないことが報告されていることがわかります。
次に、上記の反復子オブジェクト ジェネレーターを使用して、オブジェクトがfor of
トラバーサルをサポートするようにすることができます。
obj[Symbol.iterator] = function* () {
yield* this.name
}
Object.keys()
を使用してオブジェクトの値コレクションを取得しkey
、それから使用することもできます。for of
const obj = {
name: 'youhun',age: 18}
for(const key of Object.keys(obj)){
console.log(key, obj[key])
// name youhun
// age 18
}
非同期反復
[Symbol.iterator]
プロパティをデプロイする同期反復可能とは異なり、非同期反復可能には、[Symbol.asyncIterator]
このプロパティがデプロイされたというマークが付けられます。
// 用生成器生成
const obj = {
async *[Symbol.asyncIterator]() {
yield 1;
yield 2;
yield 3;
}
}
const asyncIterator = obj[Symbol.asyncIterator]()
asyncIterator.next().then(data => console.log(data)) // {value: 1, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 2, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 3, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: undefined, done: true}
これがasyncIterator
非同期イテレータです。同期反復子iterator
とは異なり、 on メソッドasyncIterator
を呼び出すと、内部値がの形式でnext
ある Promise オブジェクトが取得されます。{ value: xx, done: xx }
Promise.resolve({ value: xx, done: xx })
なぜ非同期反復があるのでしょうか?
同期イテレータのデータ取得に時間がかかる場合 (実際のシナリオでのインターフェイスの要求など)、for-of
トラバーサルを使用すると問題が発生します。
const obj = {
*[Symbol.iterator]() {
yield new Promise(resolve => setTimeout(() => resolve(1), 5000))
yield new Promise(resolve => setTimeout(() => resolve(2), 2000))
yield new Promise(resolve => setTimeout(() => resolve(3), 500))
}
}
console.log(Date.now())
for (const item of obj) {
item.then(data => console.log(Date.now(), data))
}
// 1579253648926
// 1579253649427 3 // 1579253649427 - 1579253648926 = 501
// 1579253650927 2 // 1579253650927 - 1579253648926 = 2001
// 1579253653927 1 // 1579253653927 - 1579253648926 = 5001
これらはそれぞれitem
インターフェース要求と見なすことができ、データが返されるまでの時間は必ずしも必要ではありません。上記の印刷結果は、データが処理される順序を制御できないという問題を示しています。
非同期イテレータをもう一度見てみましょう
const obj = {
async *[Symbol.asyncIterator]() {
yield new Promise(resolve => setTimeout(() => resolve(1), 5000))
yield new Promise(resolve => setTimeout(() => resolve(2), 3000))
yield new Promise(resolve => setTimeout(() => resolve(3), 500))
}
}
console.log(Date.now())
for await (const item of obj) {
console.log(Date.now(), item)
}
// 1579256590699
// 1579256595700 1 // 1579256595700 - 1579256590699 = 5001
// 1579256598702 2 // 1579256598702 - 1579256590699 = 8003
// 1579256599203 3 // 1579256599203 - 1579256590699 = 8504
非同期反復子は属性で宣言する必要があり、ループを[Symbol.asyncIterator]
使用して処理されることに注意してください。for-await-of
最終的な効果は、タスクを 1 つずつ処理し、前のタスクが処理されるのを待ってから次のタスクに進むことです。
したがって、非同期反復子は、データをすぐに取得できない状況に対処するために使用され、最終的な処理順序が走査順序と同じになるようにすることもできますが、順番に待つ必要があります。
待ちの
次のコードを使用してトラバースできます。
for await (const item of obj) {
console.log(item)
}
つまり、非同期反復トラバーサルではfor-await-of
このステートメントを使用する必要があります。非同期反復可能オブジェクトで使用するだけでなく、同期反復可能オブジェクトでも使用できます。
const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
}
}
for await(const item of obj) {
console.log(item) // 1 -> 2 -> 3
}
[Symbol.asyncIterator]
注:と が同時にオブジェクトにデプロイされている場合[Symbol.iterator]
、によって生成された非同期イテレータが最初に使用され[Symbol.asyncIterator]
ます。for-await-of
これは、もともと非同期イテレータのために生まれたものであるため、理解するのは簡単です。
逆に、2 つのイテレータが同時にデプロイされている場合は、for-or
同期イテレータが最初に使用されます。
const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
},
async *[Symbol.asyncIterator]() {
yield 4
yield 5
yield 6
}
}
// 异步
for await(const item of obj) {
console.log(item) // 4 -> 5 -> 6。优先使用由 [Symbol.asyncIterator] 生成的异步迭代器
}
// 同步
for (const item of obj) {
console.log(item) // 1 -> 2 -> 3。优先使用由 [Symbol.iterator] 生成的同步迭代器
}
要約する
イテレータ ジェネレータのロジックは少し複雑かもしれませんが、その原理を理解することが非常に必要です。自分で書いてみると、その理由がわかります。この方法でのみ、必要な実装でオブジェクトをトラバースするための独自のイテレータを定義でき、実際の開発の対応するシーンにも適用できます。