iterador
Los iteradores son una organización ordenada, secuencial y basada en extracción para consumir datos para controlar el comportamiento paso a paso.
En pocas palabras, iteramos y hacemos un bucle en un objeto iterable. En lugar de devolver todos los datos a la vez, llamamos a métodos relacionados para devolverlos en lotes.
Un iterador es un objeto que nos ayuda a recorrer una determinada estructura de datos, este object
tiene una next
función que devuelve un valor value
con un done
atributo object
, que apunta al valor definido value
por la función actual en la secuencia de iteración .next
{
done: boolean, // 为 true 时代表迭代完毕
value: any // done 为 true 时取值为 undefined
}
protocolo de iteración
El protocolo de iteración de ES6 se divide en protocolo iterador (protocolo iterador ) y protocolo iterable (protocolo iterable) , y los iteradores se implementan en función de estos dos protocolos.
Protocolo iterador: iterator协议
define value
una forma estándar de generar secuencias. next
Este objeto es un iterador siempre que implemente las funciones requeridas . Todo un puntero para recorrer los elementos de la estructura de datos, similar a un cursor en una base de datos.
Protocolo iterable: una vez que se admite el protocolo iterable, significa que el objeto for-of
se puede usar para atravesar y se puede usar para definir o personalizar el comportamiento iterativo de los objetos JS. Los tipos incorporados comunes, como Array
& Map
all, admiten el protocolo iterable. Un objeto debe implementar @@iterator
un método, lo que significa que el objeto debe tener una propiedad con una @@iterator key
constante a la que se pueda Symbol.iterator
acceder.
Simular la implementación de un iterador
Basado en el protocolo iterador
// 实现
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())
Basado en un protocolo iterable
El objeto que implementa el método de generar un iterador se llama 可迭代对象
Es decir, este objeto contiene un método que devuelve un objeto iterador
Generalmente utilizado Symbol.iterator
para definir este atributo, el nombre científico se llama @@iterator
método
// 一个可迭代对象需要具有[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
}
En los dos ejemplos de iteradores simulados anteriores, todavía es relativamente complicado, pero ES6 presenta un objeto generador , que puede facilitar mucho el proceso de creación de objetos iteradores.
Constructor
Un generador es una función que devuelve un iterador , indicado por un asterisco (*****) después de la palabra clave, y la nueva palabra clave se usará en la función .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}
En el ejemplo anterior, creatIterator()
el asterisco anterior * indica que es un generador, y el valor de retorno y el orden de retorno al yield
llamar al método iterador se especifican a través de palabras clave .next()
Cada vez que se ejecuta una declaración de rendimiento, la función dejará de ejecutarse automáticamente . Tome el ejemplo anterior como ejemplo, después de ejecutar la declaración , la función no ejecutará ningún otro idioma y la declaración no continuará yield 1
hasta que se vuelva a llamar al método iterador .next()
yield 2
Nota: yield
Las expresiones solo se pueden usar en las funciones del Generador y los errores se informarán cuando se usen en otros lugares.
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
Nota: ES6 no estipula function
dónde escribir el asterisco entre la palabra clave y el nombre de la función. Esto lleva a la siguiente escritura puede pasar.
function * foo(x, y) {
··· }
function *foo(x, y) {
··· }
function* foo(x, y) {
··· }
function*foo(x, y) {
··· }
Parámetro de paso del generador
yield
La expresión en sí misma no devuelve un valor, o siempre devuelve undefined
. next
El método puede tomar un parámetro, que se utilizará como yield
valor de retorno de la expresión anterior.
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())
Escenario de aplicación
En el desarrollo diario, cuando la siguiente interfaz depende de los datos de la interfaz anterior, el generador se puede utilizar sin tener en cuenta el problema del anidamiento del infierno de devolución de llamada asincrónica.
Simulación: obtenga datos de usuario después de 1 s, información de pedidos después de 2 s e información del producto después de 3 s
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()
para de
for of
El ciclo puede obtener el valor clave en un par de valores clave , porque este ciclo está estrechamente relacionado con el iterador, así que hablemos de eso aquí.
Symbol.iterator
Se considera que una estructura de datos tiene una iterato
interfaz R y se puede usar siempre que tenga propiedades implementadas for of
y puede recorrer objetos iterables.
JavaScript
iterable
Estructura de datos con interfaz por defecto :
- matriz matriz
- Mapa
- Colocar
- Cadena
- Objeto de argumentos
- Los objetos de lista de nodos, las matrices de clases y cualquier estructura de datos que implemente
iterator
una interfaz pueden usar el operador de distribución (...) de la matriz y operaciones como la desestructuración de asignaciones.
iterar sobre la matriz
Intente hacer un bucle en la matriz con for o
Dado que la matriz admite for...of
bucles, la matriz debe haber implementado Iterator
la interfaz, usémosla para ver Iterator
el proceso transversal de .
De la figura podemos ver:
Iterator
La interfaz devuelve unnext
objeto con métodos.- Cada llamada a next devuelve los elementos de la matriz por turnos hasta que apunta al final de la estructura de datos.
- El resultado devuelto es un objeto, que contiene el valor actual
value
y si el final actualdone
atravesar objetos
Intente atravesar el objeto, encontraremos que informó que el objeto no es iterable, como se muestra a continuación
Luego, podemos usar el generador de objetos iterador anterior para hacer que el objeto también admita for of
el recorrido
obj[Symbol.iterator] = function* () {
yield* this.name
}
También puede usar para Object.keys()
obtener la colección de valores del objeto key
y luego usarfor of
const obj = {
name: 'youhun',age: 18}
for(const key of Object.keys(obj)){
console.log(key, obj[key])
// name youhun
// age 18
}
iteración asíncrona
A diferencia de los iterables síncronos que implementan [Symbol.iterator]
la propiedad, los iterables asíncronos se marcan con [Symbol.asyncIterator]
esta propiedad implementada.
// 用生成器生成
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}
Aquí asyncIterator
está el iterador asíncrono. iterator
A diferencia del iterador síncrono , asyncIterator
llamar next
al método obtendrá un objeto Promise cuyo valor interno tiene { value: xx, done: xx }
la forma de, similar a Promise.resolve({ value: xx, done: xx })
.
¿Por qué tener iteración asíncrona?
Si la adquisición de datos del iterador síncrono lleva tiempo (como solicitar una interfaz en un escenario real), entonces for-of
habrá problemas si usa transversal.
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
Cada uno de estos puede considerarse item
como una solicitud de interfaz, y el tiempo para la devolución de datos no lo es necesariamente. Los resultados de impresión anteriores ilustran el problema: no podemos controlar el orden en que se procesan los datos.
Veamos nuevamente los iteradores asincrónicos.
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
Tenga en cuenta que el iterador asíncrono debe declararse en [Symbol.asyncIterator]
el atributo y for-await-of
se procesará mediante el bucle. El efecto final es procesar las tareas una por una y esperar a que se procese la tarea anterior antes de ingresar a la siguiente tarea.
Por lo tanto, el iterador asíncrono se usa para lidiar con la situación en la que los datos no se pueden obtener de inmediato y también puede garantizar que el orden de procesamiento final sea igual al orden transversal, pero debe esperar en línea.
para-aguardar-de
Podemos usar el siguiente código para atravesar:
for await (const item of obj) {
console.log(item)
}
Es decir, el recorrido iterativo asincrónico necesita usar for-await-of
la declaración. Además de usarse en objetos iterables asíncronos, también se puede usar en objetos iterables síncronos .
const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
}
}
for await(const item of obj) {
console.log(item) // 1 -> 2 -> 3
}
[Symbol.asyncIterator]
Nota: Si y se implementan en un objeto al mismo tiempo [Symbol.iterator]
, se usará primero [Symbol.asyncIterator]
el iterador asíncrono generado por . Esto es fácil de entender, porque for-await-of
nació originalmente para iteradores asíncronos.
Por el contrario, si se implementan dos iteradores al mismo tiempo, pero for-or
el iterador síncrono se usa primero.
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] 生成的同步迭代器
}
Resumir
La lógica del generador de iteradores puede ser un poco complicada, pero es muy necesario comprender su principio. Puedes intentar escribirlo tú mismo y saber por qué. Solo de esta manera la implementación requerida puede definir su propio iterador para atravesar el objeto, y también se puede aplicar en la escena correspondiente del desarrollo real.