La forma típica de x
tiempos de bucle en JavaScript es:
for (var i = 0; i < x; i++)
doStuff(i);
Pero no quiero usar el ++
operador o tener ninguna variable mutable en absoluto. Entonces, ¿hay alguna manera, en ES6, de recorrer los x
tiempos de otra manera? Me encanta el mecanismo de Ruby:
x.times do |i|
do_stuff(i)
end
¿Algo similar en JavaScript / ES6? Podría hacer trampa y hacer mi propio generador:
function* times(x) {
for (var i = 0; i < x; i++)
yield i;
}
for (var i of times(5)) {
console.log(i);
}
Por supuesto que todavía estoy usando i++
. Al menos está fuera de la vista :), pero espero que haya un mejor mecanismo en ES6.
Respuestas:
¡OKAY!
El siguiente código está escrito con sintaxis de ES6 pero podría escribirse con la misma facilidad en ES5 o incluso menos. ES6 no es un requisito para crear un "mecanismo para repetir x veces"
Si no necesita el iterador en la devolución de llamada , esta es la implementación más simple
Si necesita el iterador , puede usar una función interna con nombre con un parámetro de contador para iterar por usted
Pero algo debería sentirse mal sobre esos ...
if
declaraciones de una sola rama son feas, ¿qué sucede en la otra rama?undefined
- indicación de función impura, efecto secundario"¿No hay una mejor manera?"
Ahi esta. Primero revisemos nuestra implementación inicial
Claro, es simple, pero observe cómo simplemente llamamos
f()
y no hacemos nada con él. Esto realmente limita el tipo de función que podemos repetir varias veces. Incluso si tenemos el iterador disponible,f(i)
no es mucho más versátil.¿Qué pasa si comenzamos con un mejor tipo de procedimiento de repetición de funciones? Quizás algo que haga un mejor uso de la entrada y la salida.
Función genérica de repetición
Arriba, definimos una
repeat
función genérica que toma una entrada adicional que se utiliza para iniciar la aplicación repetida de una sola función.Implementando
times
conrepeat
Bueno, esto es fácil ahora; Casi todo el trabajo ya está hecho.
Dado que nuestra función toma
i
como entrada y regresai + 1
, esto efectivamente funciona como nuestro iterador al que pasamosf
cada vez.También hemos solucionado nuestra lista de problemas.
if
declaraciones feas de una sola ramaundefined
Operador de coma JavaScript, el
En caso de que tenga problemas para ver cómo funciona el último ejemplo, depende de su conocimiento de uno de los ejes de batalla más antiguos de JavaScript; el operador de coma : en resumen, evalúa las expresiones de izquierda a derecha y devuelve el valor de la última expresión evaluada
En nuestro ejemplo anterior, estoy usando
que es solo una forma sucinta de escribir
Tail Call Optimization
A pesar de lo sexy que son las implementaciones recursivas, en este punto sería irresponsable para mí recomendarlas, dado que ninguna máquina virtual JavaScript en la que pueda pensar admite la eliminación adecuada de llamadas de cola: Babel solía transpilarla, pero se ha roto, se volverá a implementar "estado por más de un año.
Como tal, deberíamos revisar nuestra implementación de
repeat
para que sea apilable.El código siguiente hace utilizar variables mutables
n
yx
pero tenga en cuenta que todas las mutaciones se localizan en larepeat
función - no hay cambios de estado (mutaciones) son visibles desde fuera de la funciónEsto hará que muchos de ustedes digan "¡pero eso no es funcional!" - Lo sé, solo relájate. Podemos implementar una interfaz
loop
/ estilo Clojurerecur
para bucles de espacio constante usando expresiones puras ; Ninguna de esaswhile
cosas.Aquí abstraemos
while
nuestraloop
función: busca unrecur
tipo especial para mantener el ciclo en ejecución. Cuandorecur
se encuentra un no tipo, el ciclo finaliza y se devuelve el resultado del cálculo.fuente
g => g(g)(x)
). ¿Hay algún beneficio de una función de orden superior sobre una de primer orden, como en mi solución?Usando el operador ES2015 Spread :
[...Array(n)].map()
O si no necesitas el resultado:
O utilizando el operador ES2015 Array.from :
Array.from(...)
Tenga en cuenta que si solo necesita una cadena repetida, puede usar String.prototype.repeat .
fuente
Array.from(Array(10), (_, i) => i*10)
[...Array(10)].forEach(() => console.log('looping 10 times');
fuente
Array
se usan las teclas.[0..x]
haskell en JS más conciso que en mi respuesta.Array.prototype.keys
eObject.prototype.keys
, pero seguro que es confuso a primera vista.Creo que la mejor solución es usar
let
:Eso creará una nueva
i
variable (mutable) para cada evaluación del cuerpo y asegura quei
solo se cambie en la expresión de incremento en esa sintaxis de bucle, no en ningún otro lugar.Eso debería ser suficiente imo. Incluso en lenguajes puros, todas las operaciones (o al menos, sus intérpretes) se construyen a partir de primitivas que usan mutación. Mientras tenga el alcance adecuado, no puedo ver qué hay de malo en eso.
Deberías estar bien con
Entonces su única opción es usar la recursividad. Puede definir esa función del generador sin un mutable
i
también:Pero eso me parece excesivo y podría tener problemas de rendimiento (ya que la eliminación de llamadas de cola no está disponible
return yield*
).fuente
Este fragmento lo hará
console.log
test
4 veces.fuente
Creo que es bastante simple:
o
fuente
Respuesta: 09 de diciembre de 2015
Personalmente, encontré la respuesta aceptada tanto concisa (buena) como concisa (mala). Apreciamos que esta declaración puede ser subjetiva, así que lea esta respuesta y vea si está de acuerdo o en desacuerdo
El ejemplo dado en la pregunta fue algo así como el de Ruby:
Expresar esto en JS usando a continuación permitiría:
Aquí está el código:
¡Eso es!
Ejemplo de uso simple:
Alternativamente, siguiendo los ejemplos de la respuesta aceptada:
Nota al margen: definición de una función de rango
Una pregunta similar / relacionada, que utiliza construcciones de código fundamentalmente muy similares, podría ser si existe una función de Rango conveniente en JavaScript (núcleo), algo similar a la función de rango de subrayado.
Crea una matriz con n números, comenzando desde x
Guion bajo
ES2015
Par de alternativas:
Demostración usando n = 10, x = 1:
En una prueba rápida que ejecuté, cada una de las anteriores se ejecutó un millón de veces utilizando nuestra solución y la función doStuff, el enfoque anterior (Array (n) .fill ()) resultó un poco más rápido.
fuente
Esta versión satisface los requisitos de OP para la inmutabilidad. También considere usar en
reduce
lugar demap
depender de su caso de uso.Esta también es una opción si no te importa un poco de mutación en tu prototipo.
Ahora podemos hacer esto
+1 a arcseldon por la
.fill
sugerencia.fuente
Aquí hay otra buena alternativa:
Preferiblemente, como señaló @Dave Morse en los comentarios, también puede deshacerse de la
map
llamada, utilizando el segundo parámetro de laArray.from
función de la siguiente manera:fuente
Array.from
en MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Array.from({ length: label.length }, (_, i) => (...))
esto ahorra la creación de una matriz temporal vacía solo para iniciar una llamada al mapa.No es algo que enseñe (o use en mi código), pero aquí hay una solución digna de codegolf sin mutar una variable, sin necesidad de ES6:
Más una cosa interesante de prueba de concepto que una respuesta útil, realmente.
fuente
Array.apply(null, {length: 10})
ser justoArray(10)
?Llego tarde a la fiesta, pero como esta pregunta aparece a menudo en los resultados de búsqueda, me gustaría agregar una solución que considero la mejor en términos de legibilidad sin ser larga (lo cual es ideal para cualquier IMO de base de código) . Muta, pero haría esa compensación por los principios de KISS.
fuente
times
variable dentro del bucle. Quizáscountdown
sería un mejor nombre. De lo contrario, la respuesta más clara y clara en la página.Afaik, no hay ningún mecanismo en ES6 similar al de Ruby
times
método . Pero puede evitar la mutación usando la recursividad:Demostración: http://jsbin.com/koyecovano/1/edit?js,console
fuente
Si está dispuesto a usar una biblioteca, también hay lodash
_.times
o guión bajo_.times
:Tenga en cuenta que esto devuelve una serie de resultados, por lo que es realmente más parecido a este rubí:
fuente
En el paradigma funcional
repeat
suele ser una función recursiva infinita. Para usarlo necesitamos una evaluación perezosa o un estilo de pase de continuación.Lazy evaluó la repetición de funciones
Utilizo un thunk (una función sin argumentos) para lograr una evaluación perezosa en Javascript.
Repetición de funciones con estilo de paso continuo
CPS da un poco de miedo al principio. Sin embargo, siempre sigue el mismo patrón: El último argumento es la continuación (una función), que invoca su propio cuerpo:
k => k(...)
. Tenga en cuenta que CPS convierte la aplicación al revés, es decir, setake(8) (repeat...)
convierte enk(take(8)) (...)
dóndek
se aplica parcialmenterepeat
.Conclusión
Al separar la repetición (
repeat
) de la condición de terminación (take
) obtenemos flexibilidad, separación de preocupaciones hasta su amargo final: Dfuente
Ventajas de esta solución
Desventajas - Mutación. Siendo interno solo no me importa, tal vez algunos otros tampoco.
Ejemplos y código
Versión TypeScipt
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011
fuente
abordando el aspecto funcional:
fuente
i
. ¿Cuál es la razón para incluso usartimes
más de lo normalfor
entonces?var twice = times(2);
.for
dos veces?i++
. No es obvio cómo envolver algo inaceptable en una función lo mejora.Generadores? Recursividad? ¿Por qué tanto odio a la mutatina? ;-)
Si es aceptable siempre que lo "ocultemos", simplemente acepte el uso de un operador unario y podemos simplificar las cosas :
Como en rubí:
fuente
Envolví la respuesta de @Tieme con una función auxiliar.
En TypeScript:
Ahora puedes ejecutar:
fuente
Yo hice esto:
Uso:
La
i
variable devuelve la cantidad de veces que se ha repetido, útil si necesita precargar una cantidad x de imágenes.fuente