Este artículo le brinda conocimientos relevantes sobre JavaScript. Presenta principalmente el iterador y generador de front-end de JavaScript. Los amigos que lo necesiten pueden consultarlo.
Curso de entrada al dominio de front-end (vue): ingrese al aprendizaje
Iterator proporciona un mecanismo de interfaz unificado para proporcionar un mecanismo de acceso unificado para varias estructuras de datos diferentes.
Definir Iterator es proporcionar un objeto con un método next (). Cada vez que se llama a next (), se devolverá un objeto de resultado. El objeto de resultado tiene dos atributos, el valor representa el valor actual y listo representa si se completó el recorrido. .
función makeIterator(Matriz){ dejar índice = 0; devolver { siguiente: función(){ devolver ( Matriz.longitud > índice? {valor: Matriz[índice++]}: {hecho: verdadero} ) } } } let iterador = makeIterator(['1','2']) console.log(iterator.next()); // {valor: '1'} console.log(iterator.next()); // {valor: '2'} console.log(iterator.next()); // {hecho: verdadero}
El papel del iterador:
Proporcionar una interfaz de acceso unificada y sencilla para diversas estructuras de datos;
Permite organizar los miembros de una estructura de datos en un orden determinado;
para consumo por para...de
ES6 proporciona la instrucción for para atravesar el objeto iterador. Usaremos la instrucción for para atravesar el iterador creado anteriormente:
let iterador = makeIterator(['1','2']) for (dejar valor del iterador) { consola.log(valor); } // el iterador no es iterable
El resultado es un error que indica que el iterador no es iterable. ¿Por qué ocurre esto? ES6 estipula que la interfaz Iterator predeterminada se implementa en la propiedad Symbol.iterator de la estructura de datos. Si una estructura de datos tiene la propiedad Symbol.iterator, la estructura de datos se puede atravesar.
Transformamos el makeIterator personalizado de la siguiente manera:
const MakeIterator = (Matriz) => ({ [Símbolo.iterador](){ dejar índice = 0; devolver { próximo(){ let longitud = Array.longitud; si(índice <longitud){ devolver {valor: Matriz[índice++]} }demás{ devolver {hecho: verdadero} } } } } }) for(let valor de MakeIterator([1,2])){ consola.log(valor) } // 1 // 2
Agregamos un método de retorno a MakeIterator Si el bucle for...of sale antes (generalmente debido a un error o una declaración de interrupción), se llamará al método return() para finalizar el recorrido.
Según esta característica, si un objeto necesita limpiar o liberar recursos antes de completar el recorrido, podemos implementar el método return() e incluir el cierre del archivo cuando falla la lectura del archivo.
const MakeIterator = (Matriz) => ({ [Símbolo.iterador](){ dejar índice = 0; devolver { próximo(){ let longitud = Array.longitud; si(índice <longitud){ devolver {valor: Matriz[índice++]} }demás{ devolver {hecho: verdadero} } }, devolver(){ devolver {hecho: verdadero} } } } }) for(let valor de MakeIterator([1, 2, 3])){ consola.log(valor) // 1 // Método 1 romper; // Método 2 // arrojar nuevo Error('error'); }
formación
Colocar
Mapa
Objetos tipo matriz, como objetos de argumentos, objetos DOM NodeList, objetos typedArray
// argumentos objeto función suma(){ for(let valor de los argumentos){ consola.log(valor) } } suma(1,2) // 1 // 2 // objeto typedArray let typeArry = new Int8Array(2); tipoArry[0] = 1; tipoArry[1] = 2; for(dejar valor de typeArry){ consola.log(valor) } // 1 // 2
Objeto generador
función*gen(){ rendimiento 1; rendimiento 2; } for(let valor de gen()){ consola.log(valor) }
Cadena
P: ¿Por qué Object no tiene un iterador nativo?
R: La razón por la que Object no implementa la interfaz Iterator de forma predeterminada es porque no está claro qué propiedad del objeto se atraviesa primero y qué propiedad se atraviesa después.
En esencia, el recorrido es un proceso lineal. Para cualquier estructura de datos no lineal, implementar la interfaz del recorrido es equivalente a implementar una transformación lineal.
Sin embargo, estrictamente hablando, la interfaz transversal de implementación de objetos no es necesaria, porque en este momento el objeto en realidad se usa como una estructura de mapa. ES5 no tiene una estructura de mapa, pero ES6 la proporciona de forma nativa.
Tarea de desestructuración
let set = new Set().add('a').add('b').add('c'); dejar [x,y] = establecer; // x='a';
operador de propagación
var cadena = 'hola'; [...cadena] // ['h','e','l','l','o']
El operador de extensión llama a la interfaz Iterator, por lo que Object no implementa la interfaz Iterator, entonces, ¿por qué se puede usar el operador...?
Motivo: hay dos tipos de operadores de spread
Uno se utiliza en el caso de parámetros de función y expansión de matriz. En este caso, se requiere que el objeto sea iterable (iterable).
El otro es para la expansión de objetos, es decir, en forma {...obj}. En este caso, el objeto debe ser enumerable.
dejar obj1 = { nombre: 'qianxun' } dejar obj2 = { edad: 3 } // El objeto de matriz es enumerable let obj = {...obj1, ...obj2} console.log(obj) //{nombre: 'qianxun', edad: 3} // Los objetos ordinarios no son iterables por defecto let obj = [...obj1, ...obj2] console.log(obj) // el objeto no es iterable
función paraOf(obj, cb){ let iteratorValue = obj[Symbol.iterator](); dejar resultado = iteratorValue.next() mientras(!resultado.hecho){ cb(valor.resultado) resultado = iteradorValue.siguiente() } } forOf([1,2,3], (valor)=>{ consola.log(valor) }) // 1 // 2 // 3
Conceptualmente
La función de generador es una solución de programación asincrónica proporcionada por ES6. La función Generador es una máquina de estados que encapsula múltiples estados internos;
La función Generador también es una función de generación de objetos transversales, que devuelve un objeto transversal después de la ejecución.
formal
1. Hay un asterisco entre la palabra clave de la función y el nombre de la función;
2. Las expresiones de rendimiento se utilizan dentro del cuerpo de la función para definir diferentes estados internos.
función* generadorsimple(){ rendimiento 1; rendimiento 2; } generador simple()
Como arriba, creamos un Generador simple y lo exploramos con dos preguntas:
¿Qué sucede después de que se ejecuta la función Generador?
¿Qué hace la expresión de rendimiento en la función?
función* generadorsimple(){ console.log('hola mundo'); rendimiento 1; rendimiento 2; } let generador = simpleGenerator(); // simpleGenerator {<suspendido}} consola.log(generador.siguiente()) // Hola Mundo // {valor: 1, hecho: falso} consola.log(generador.siguiente()) // {valor: 2, hecho: falso}
La función del generador Generador devuelve un objeto generador después de ejecutarse, mientras que la función ordinaria ejecutará directamente el código dentro de la función cada vez que se llame al siguiente método del objeto generador, la función se ejecutará hasta que la siguiente palabra clave de rendimiento detenga la ejecución; un objeto {valor: Valor, hecho: booleano}.
La expresión de rendimiento en sí no tiene valor de retorno o siempre devuelve un valor indefinido. El siguiente método puede tomar un parámetro, que será tratado como el valor de retorno de la expresión de rendimiento anterior. A través de los parámetros del siguiente método, se pueden inyectar diferentes valores desde el exterior hacia el interior en diferentes etapas de la función Generador para ajustar el comportamiento de la función. Dado que los parámetros del siguiente método representan el valor de retorno de la expresión de rendimiento anterior, pasar parámetros no es válido la primera vez que se utiliza el siguiente método.
función suma(x){ función de retorno (y) { devolver x + y; } } consola.log(suma(1)(2)) // Usa el siguiente parámetro para reescribir la función* sum(x){ sea y = rendimiento x; mientras (verdadero) { y = rendimiento x + y; } } sea gen = suma(2) consola.log(gen.next()) // 2 consola.log(gen.next(1)) // 3 consola.log(gen.next(2)) // 4
El papel de la expresión de rendimiento: definir el estado interno y pausar la ejecución La diferencia entre la expresión de rendimiento y la declaración de devolución.
La expresión de rendimiento significa que la función pausa la ejecución y continúa ejecutándose hacia atrás desde esa posición la próxima vez, mientras que la declaración de retorno no tiene la función de memoria de posición.
En una función, solo se puede ejecutar una declaración de retorno, pero se pueden ejecutar múltiples expresiones de rendimiento.
Cualquier función puede usar la declaración de retorno. La expresión de rendimiento solo se puede usar en la función Generador. Si se usa en otro lugar, se informará un error.
Si la expresión de rendimiento participa en la operación, colóquela entre paréntesis; si se usa como parámetro de función o se coloca en el lado derecho de la expresión de asignación, es posible que los paréntesis no se incluyan.
función *gen() { console.log('hola' + rendimiento) × console.log('hola' + (rendimiento)) √ console.log('hola' + rendimiento 1) × console.log('hola' + (rendimiento 1)) √ foo(rendimiento 1) √ parámetro constante = rendimiento 2 √ }
Basado en el hecho de que la función del generador Generador puede admitir múltiples rendimientos, podemos implementar un escenario en el que una función tiene múltiples valores de retorno:
función* gen(núm1, núm2){ producir número1 + número2; rendimiento num1 - num2; } sea res = gen(2, 1); console.log(res.next()) // {valor: 3, hecho: falso} console.log(res.next()) // {valor: 1, hecho: falso}
Dado que la función Generador es la función de generación de iteradores, el Generador se puede asignar a la propiedad Symbol.iterator del objeto, de modo que el objeto tenga la interfaz Iterador. El código de implementación del generador es más conciso.
dejar objeto = { nombre: 'qianxun', edad: 3, [Símbolo.iterador]: función(){ deja eso = esto; let llaves = Objeto.claves(eso) dejar índice = 0; devolver { siguiente: función(){ índice de retorno <claves.longitud? {valor: eso[claves[índice++]], hecho: falso}: {valor: indefinido, hecho: verdadero} } } } } for(dejar valor de obj){ consola.log(valor) }
Generador:
dejar objeto = { nombre: 'qianxun', edad: 3, [Símbolo.iterador]: función* (){ let llaves = Objeto.claves(esto) for(let i=0; i< claves.longitud; i++){ producir esto[claves[i]]; } } } for(dejar valor de obj){ consola.log(valor) }
El método
return()
puede devolver el valor dado y finalizar la función del Generador transversal.
función*gen() { rendimiento 1; rendimiento 2; rendimiento 3; } var g = gen(); g.next() // { valor: 1, hecho: falso } // Si no se proporcionan parámetros cuando se llama al método return(), el atributo de valor del valor de retorno no está definido g.return('foo') // { valor: "foo", hecho: verdadero } g.next() // { valor: indefinido, hecho: verdadero }
Si hay try...finally
dentro de la función Generador y el bloque de código try
se está ejecutando, entonces return()
hará que finally
se ingrese inmediatamente. Después de la ejecución, toda la función finalizará.
función* números () { rendimiento 1; intentar { rendimiento 2; rendimiento 3; } finalmente { rendimiento 4; rendimiento 5; } rendimiento 6; } var g = números(); g.next() // { valor: 1, hecho: falso } g.next() // { valor: 2, hecho: falso } g.return(7) // { valor: 4, hecho: falso } g.next() // { valor: 5, hecho: falso } g.next() // { valor: 7, hecho: verdadero }
Si desea llamar a otra función Generador dentro de la función Generador. Necesitamos completar manualmente el recorrido dentro del cuerpo de la función del primero. Si la llamada a la función está anidada en varios niveles, la escritura será engorrosa y difícil de leer. ES6 proporciona expresiones de rendimiento* como solución.
Delegar a otros generadores
función* g1() { rendimiento 2; rendimiento 3; } función* g2() { rendimiento 1; rendimiento* g1(); rendimiento 4; } iterador constante = g2(); console.log(iterator.next()); // { valor: 1, hecho: falso } console.log(iterator.next()); // { valor: 2, hecho: falso } console.log(iterator.next()); // { valor: 3, hecho: falso } console.log(iterator.next()); // { valor: 4, hecho: falso } console.log(iterator.next()); // { valor: indefinido, hecho: verdadero }
Delegar a otros objetos iterables
función*gen(){ rendimiento* [1,2,3] } console.log(gen().next()) // {valor: 1, hecho: falso}
La función Generador devuelve un recorrido. ES6 estipula que este recorrido es una instancia de la función Generador y hereda los métodos del objeto Generator.prototype, pero no puede obtener las propiedades de este porque este es el objeto global en este momento, no la instancia. objeto.
función*gen(){ esto.a = 1 } gen.prototipo.say = función(){ console.log('hola') } dejar obj = gen() console.log(obj instanciade gen) // verdadero obj.say() // hola obj.siguiente() console.log(obj.a) //indefinido
Si desea acceder a las propiedades de la instancia como un constructor, puede modificar esto para vincularlo a Generator.prototype.
función*gen(){ esto.a = 1 } gen.prototipo.say = función(){ console.log('hola') } let obj = gen.call(gen.prototipo) console.log(obj instanciade gen) // verdadero obj.say() // hola obj.siguiente() consola.log(obj.a) //1
función* MáquinaEstado(estado){ dejar la transición; mientras (verdadero) { if(transición === "INCREMENTO"){ estado++; }si no(transición === "DECREMENTO"){ estado--; } transición = estado productivo; } } iterador constante = StateMachine(0); consola.log(iterator.next()); // 0 console.log(iterator.next('INCREMENT')); // 1 console.log(iterator.next('DECREMENTO')); // 0