A maneira típica de repetir os x
tempos no JavaScript é:
for (var i = 0; i < x; i++)
doStuff(i);
Mas não quero usar o ++
operador ou ter nenhuma variável mutável. Então, existe uma maneira, no ES6, de repetir os x
tempos de outra maneira? Eu amo o mecanismo de Ruby:
x.times do |i|
do_stuff(i)
end
Algo semelhante no JavaScript / ES6? Eu poderia meio que trapacear e criar meu próprio gerador:
function* times(x) {
for (var i = 0; i < x; i++)
yield i;
}
for (var i of times(5)) {
console.log(i);
}
Claro que ainda estou usando i++
. Pelo menos está fora de vista :), mas espero que exista um mecanismo melhor no ES6.
Respostas:
ESTÁ BEM!
O código abaixo é escrito usando as sintaxes do ES6, mas poderia ser facilmente escrito no ES5 ou até menos. O ES6 não é um requisito para criar um "mecanismo para repetir x vezes"
Se você não precisar do iterador no retorno de chamada , esta é a implementação mais simples
Se você precisar do iterador , poderá usar uma função interna nomeada com um parâmetro counter para iterar para você
Mas algo deve estar errado com aqueles ...
if
declarações de ramo único são feias - o que acontece no outro ramo?undefined
- indicação de função impura e com efeitos colaterais"Não existe uma maneira melhor?"
Há sim. Vamos revisitar primeiro nossa implementação inicial
Claro, é simples, mas observe como ligamos
f()
e não fazemos nada com isso. Isso realmente limita o tipo de função que podemos repetir várias vezes. Mesmo que tenhamos o iterador disponível,f(i)
não é muito mais versátil.E se começarmos com um tipo melhor de procedimento de repetição de função? Talvez algo que faça melhor uso de entrada e saída.
Repetição de função genérica
Acima, definimos uma
repeat
função genérica que recebe uma entrada adicional que é usada para iniciar a aplicação repetida de uma única função.Implementando
times
comrepeat
Bem, isso é fácil agora; quase todo o trabalho já está feito.
Como nossa função assume
i
como entrada e retornai + 1
, isso efetivamente funciona como nosso iterador, ao qual passamos af
cada vez.Também corrigimos nossa lista de problemas
if
declarações de ramo único feiasundefined
Operador de vírgula JavaScript, o
Caso esteja com problemas para ver como o último exemplo está funcionando, isso depende da sua consciência de um dos eixos de batalha mais antigos do JavaScript; o operador vírgula - em resumo, avalia expressões da esquerda para a direita e retorna o valor da última expressão avaliada
No exemplo acima, estou usando
que é apenas uma maneira sucinta de escrever
Otimização de chamada de cauda
Por mais sexy que sejam as implementações recursivas, neste momento seria irresponsável para mim recomendá-las, já que nenhuma VM JavaScript que eu possa imaginar suporta a eliminação adequada de chamadas de cauda - o babel usado para transpilar, mas está "quebrado; será reimplementado" "status por mais de um ano.
Como tal, devemos revisitar nossa implementação
repeat
para torná-la segura para a pilha.O código a seguir faz usar variáveis mutáveis
n
ex
mas note que todas as mutações estão localizadas àrepeat
função - nenhuma alteração de estado (mutações) são visíveis a partir do exterior da funçãoMuitos vão dizer "mas isso não é funcional!" Eu sei, apenas relaxe. Podemos implementar uma interface
loop
/ estilo Clojurerecur
para loop de espaço constante usando expressões puras ; nada dissowhile
.Aqui, abstraímos
while
nossaloop
função - ela procura umrecur
tipo especial para manter o loop em execução. Quando um não-recur
tipo é encontrado, o loop é concluído e o resultado da computação é retornado.fonte
g => g(g)(x)
). Existe um benefício de uma função de ordem superior à de uma ordem de primeira ordem, como na minha solução?Usando o operador ES2015 Spread :
[...Array(n)].map()
Ou se você não precisar do resultado:
Ou usando o operador ES2015 Array.from :
Array.from(...)
Observe que, se você apenas precisar de uma sequência repetida, poderá usar String.prototype.repeat .
fonte
Array.from(Array(10), (_, i) => i*10)
[...Array(10)].forEach(() => console.log('looping 10 times');
fonte
Array
chaves são usadas.[0..x]
no JS mais conciso do que na minha resposta.Array.prototype.keys
eObject.prototype.keys
, mas com certeza é confuso à primeira vista.Eu acho que a melhor solução é usar
let
:Isso criará uma nova
i
variável (mutável) para cada avaliação do corpo e garantirá que issoi
seja alterado apenas na expressão de incremento na sintaxe do loop, e não em qualquer outro lugar.Isso deve ser suficiente. Mesmo em idiomas puros, todas as operações (ou pelo menos seus intérpretes) são construídas a partir de primitivos que usam mutação. Contanto que tenha um escopo adequado, não vejo o que há de errado nisso.
Você deveria ficar bem com
Então sua única opção é usar recursão. Você pode definir essa função do gerador sem um mutável
i
também:Mas isso me parece um exagero e pode ter problemas de desempenho (já que a eliminação de chamadas não está disponível
return yield*
).fonte
Este trecho será
console.log
test
4 vezes.fonte
Eu acho que é bem simples:
ou
fonte
Resposta: 09 de dezembro de 2015
Pessoalmente, achei a resposta aceita concisa (boa) e concisa (ruim). Aprecie que esta declaração possa ser subjetiva. Leia esta resposta e veja se você concorda ou discorda.
O exemplo dado na pergunta era algo como Ruby:
Expressar isso em JS usando abaixo permitiria:
Aqui está o código:
É isso aí!
Exemplo de uso simples:
Como alternativa, siga os exemplos da resposta aceita:
Nota lateral - Definindo uma função de faixa
Uma pergunta semelhante / relacionada, que usa construções de código fundamentalmente muito semelhantes, pode ser que exista uma função Range conveniente no JavaScript (principal), algo semelhante à função range do sublinhado.
Crie uma matriz com n números, começando com x
Sublinhado
ES2015
Duas alternativas:
Demonstração usando n = 10, x = 1:
Em um teste rápido, eu executei, com cada uma das opções acima executando um milhão de vezes cada uma, usando nossa solução e função doStuff, a abordagem anterior (Array (n) .fill ()) se mostrou um pouco mais rápida.
fonte
Esta versão atende aos requisitos de imutabilidade do OP. Considere também usar em
reduce
vez demap
depender do seu caso de uso.Essa também é uma opção se você não se importar com uma pequena mutação no seu protótipo.
Agora podemos fazer isso
+1 para arcseldon para a
.fill
sugestão.fonte
Aqui está outra boa alternativa:
De preferência, como o @Dave Morse apontou nos comentários, você também pode se livrar da
map
chamada usando o segundo parâmetro daArray.from
função da seguinte maneira:fonte
Array.from
no MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Array.from({ length: label.length }, (_, i) => (...))
Isso economiza a criação de um array temporário vazio apenas para iniciar uma chamada para o mapa.Não é algo que eu ensinaria (ou nunca usaria no meu código), mas aqui está uma solução digna de codegolf sem alterar uma variável, sem a necessidade do ES6:
Mais uma coisa interessante de prova de conceito do que uma resposta útil, na verdade.
fonte
Array.apply(null, {length: 10})
ser justoArray(10)
?Estou atrasado para a festa, mas como essa pergunta aparece com frequência nos resultados de pesquisa, gostaria de adicionar uma solução que considero a melhor em termos de legibilidade e por não ser longa (o que é ideal para qualquer IMO da base de código) . Ele sofre mutações, mas eu faria essa troca pelos princípios do KISS.
fonte
times
variável dentro do loop. Talvezcountdown
fosse um nome melhor. Caso contrário, a resposta mais limpa e clara da página.Afaik, não há mecanismo no ES6 semelhante ao
times
método Ruby . Mas você pode evitar a mutação usando a recursão:Demonstração: http://jsbin.com/koyecovano/1/edit?js,console
fonte
Se você deseja usar uma biblioteca, também há lodash
_.times
ou sublinhado_.times
:Observe que isso retorna uma matriz de resultados, por isso é realmente mais parecido com este ruby:
fonte
No paradigma funcional
repeat
é geralmente uma função recursiva infinita. Para usá-lo, precisamos de um estilo de avaliação lenta ou de aprovação de continuação.Repetição preguiçosa da função avaliada
Eu uso um thunk (uma função sem argumentos) para obter uma avaliação lenta em Javascript.
Repetição de funções com estilo de passagem contínua
CPS é um pouco assustador no começo. No entanto, ele sempre segue o mesmo padrão: O último argumento é a continuação (a função), que invoca seu próprio corpo:
k => k(...)
. Observe que o CPS inverte o aplicativo, ou seja,take(8) (repeat...)
torna-sek(take(8)) (...)
ondek
é o parcialmente aplicadorepeat
.Conclusão
Ao separar a repetição (
repeat
) da condição de terminação (take
), obtemos flexibilidade - separação de preocupações até seu amargo fim: Dfonte
Vantagens desta solução
Desvantagens - Mutação. Sendo apenas interno, eu não me importo, talvez alguns outros também não.
Exemplos e Código
Versão TypeScipt
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011
fonte
abordando o aspecto funcional:
fonte
i
. Qual é a razão para usar atétimes
mais velho do quefor
nunca?var twice = times(2);
.for
duas vezes?i++
. Não é óbvio como envolver algo inaceitável em uma função a torna melhor.Geradores? Recursão? Por que tanto odiando mutantes? ;-)
Se for aceitável desde que o "oculte", aceite o uso de um operador unário e podemos manter as coisas simples :
Assim como no ruby:
fonte
Embrulhei a resposta do @Tieme com uma função auxiliar.
No TypeScript:
Agora você pode executar:
fonte
Eu fiz isso:
Uso:
A
i
variável retorna a quantidade de vezes que foi repetida - útil se você precisar pré-carregar uma quantidade x de imagens.fonte