Existe-t-il un mécanisme pour boucler x fois dans ES6 (ECMAScript 6) sans variables mutables?

157

La manière typique de boucler les xtemps en JavaScript est:

for (var i = 0; i < x; i++)
  doStuff(i);

Mais je ne veux pas utiliser l' ++opérateur ou avoir des variables mutables du tout. Y a-t-il donc un moyen, dans ES6, de boucler les xtemps d'une autre manière? J'adore le mécanisme de Ruby:

x.times do |i|
  do_stuff(i)
end

Quelque chose de similaire dans JavaScript / ES6? Je pourrais en quelque sorte tricher et créer mon propre générateur:

function* times(x) {
  for (var i = 0; i < x; i++)
    yield i;
}

for (var i of times(5)) {
  console.log(i);
}

Bien sûr, j'utilise toujours i++. Au moins, c'est hors de vue :), mais j'espère qu'il y a un meilleur mécanisme dans ES6.

à.
la source
3
Pourquoi la variable de contrôle de boucle mutable pose-t-elle un problème? Juste un principe?
doldt
1
@doldt - J'essaie d'enseigner JavaScript, mais j'essaie de retarder le concept de variables mutables à plus tard
à.
5
Nous sommes vraiment hors sujet ici, mais êtes-vous sûr que passer aux générateurs ES6 (ou à tout autre nouveau concept de haut niveau) est une bonne idée avant qu'ils ne se familiarisent avec les variables mutables? :)
doldt
5
@doldt - peut-être, j'expérimente. Adopter une approche de langage fonctionnel pour JavaScript.
à.
Utilisez let pour déclarer cette variable dans la boucle. Sa portée se termine par la boucle.
ncmathsadist le

Réponses:

156

D'ACCORD!

Le code ci-dessous est écrit en utilisant les syntaxes ES6 mais pourrait tout aussi bien être écrit en ES5 ou même moins. ES6 n'est pas une exigence pour créer un "mécanisme pour boucler x fois"


Si vous n'avez pas besoin de l'itérateur dans le rappel , c'est l'implémentation la plus simple

const times = x => f => {
  if (x > 0) {
    f()
    times (x - 1) (f)
  }
}

// use it
times (3) (() => console.log('hi'))

// or define intermediate functions for reuse
let twice = times (2)

// twice the power !
twice (() => console.log('double vision'))

Si vous avez besoin de l'itérateur , vous pouvez utiliser une fonction interne nommée avec un paramètre de compteur pour itérer pour vous

const times = n => f => {
  let iter = i => {
    if (i === n) return
    f (i)
    iter (i + 1)
  }
  return iter (0)
}

times (3) (i => console.log(i, 'hi'))


Arrêtez de lire ici si vous n'aimez pas apprendre plus de choses ...

Mais quelque chose devrait se sentir mal à propos de ceux-ci ...

  • les ifdéclarations de branche unique sont laides - que se passe-t-il sur l'autre branche?
  • déclarations / expressions multiples dans les corps de fonction - les problèmes de procédure sont-ils mélangés?
  • renvoyé implicitement undefined- indication d'une fonction impure à effet secondaire

"N'y a-t-il pas un meilleur moyen?"

Il y a. Revoyons d'abord notre implémentation initiale

// times :: Int -> (void -> void) -> void
const times = x => f => {
  if (x > 0) {
    f()               // has to be side-effecting function
    times (x - 1) (f)
  }
}

Bien sûr, c'est simple, mais remarquez comment nous appelons f()et ne faisons rien avec. Cela limite vraiment le type de fonction que nous pouvons répéter plusieurs fois. Même si nous avons l'itérateur disponible, ce f(i)n'est pas beaucoup plus polyvalent.

Et si nous commençons avec un meilleur type de procédure de répétition de fonction? Peut-être quelque chose qui fait un meilleur usage des entrées et des sorties.

Répétition de fonction générique

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// power :: Int -> Int -> Int
const power = base => exp => {
  // repeat <exp> times, <base> * <x>, starting with 1
  return repeat (exp) (x => base * x) (1)
}

console.log(power (2) (8))
// => 256

Ci-dessus, nous avons défini une repeatfonction générique qui prend une entrée supplémentaire qui est utilisée pour démarrer l'application répétée d'une seule fonction.

// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)

// is the same as ...
var result = f(f(f(x)))

Mettre timesen œuvre avecrepeat

Eh bien, c'est facile maintenant; presque tout le travail est déjà fait.

// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
  if (n > 0)
    return repeat (n - 1) (f) (f (x))
  else
    return x
}

// times :: Int -> (Int -> Int) -> Int 
const times = n=> f=>
  repeat (n) (i => (f(i), i + 1)) (0)

// use it
times (3) (i => console.log(i, 'hi'))

Puisque notre fonction prend icomme entrée et retourne i + 1, cela fonctionne effectivement comme notre itérateur auquel nous passonsf chaque fois.

Nous avons également corrigé notre liste de problèmes

  • Plus de branche unique laide if déclarations de
  • Les corps à expression unique indiquent des préoccupations bien séparées
  • Plus inutile, retourné implicitement undefined

Opérateur de virgule JavaScript, le

Au cas où vous auriez du mal à voir comment le dernier exemple fonctionne, cela dépend de votre connaissance de l'un des plus anciens axes de combat de JavaScript; l' opérateur virgule - en bref, il évalue les expressions de gauche à droite et renvoie la valeur de la dernière expression évaluée

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

Dans notre exemple ci-dessus, j'utilise

(i => (f(i), i + 1))

qui n'est qu'une manière succincte d'écrire

(i => { f(i); return i + 1 })

Optimisation des appels de queue

Aussi sexy que soient les implémentations récursives, à ce stade, il serait irresponsable de ma part de les recommander étant donné qu'aucune VM JavaScript à laquelle je pense ne prend en charge l'élimination correcte des appels de queue - babel avait l'habitude de la transpiler, mais elle a été "cassée; sera réimplémentée "statut depuis plus d'un an.

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

À ce titre, nous devrions revoir notre mise en œuvre de repeat pour la rendre sûre.

Le code ci - dessous utilise des variables mutables net xnotez que toutes les mutations sont localisées dans la repeatfonction - aucun changement d'état (mutation) n'est visible de l'extérieur de la fonction

// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
  {
    let m = 0, acc = x
    while (m < n)
      (m = m + 1, acc = f (acc))
    return acc
  }

// inc :: Int -> Int
const inc = x =>
  x + 1

console.log (repeat (1e8) (inc) (0))
// 100000000

Vous serez nombreux à dire "mais ce n'est pas fonctionnel!" - Je sais, détends-toi. Nous pouvons implémenter une interface loop/ style Clojure recurpour la boucle d'espace constant en utilisant des expressions pures ; rien de tout whileça.

Ici, nous résumons whilenotre loopfonction - elle recherche un recurtype spécial pour maintenir la boucle en cours d'exécution. Lorsqu'un non- recurtype est rencontré, la boucle est terminée et le résultat du calcul est retourné

const recur = (...args) =>
  ({ type: recur, args })
  
const loop = f =>
  {
    let acc = f ()
    while (acc.type === recur)
      acc = f (...acc.args)
    return acc
  }

const repeat = $n => f => x =>
  loop ((n = $n, acc = x) =>
    n === 0
      ? acc
      : recur (n - 1, f (acc)))
      
const inc = x =>
  x + 1

const fibonacci = $n =>
  loop ((n = $n, a = 0, b = 1) =>
    n === 0
      ? a
      : recur (n - 1, b, a + b))
      
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100))        // 354224848179262000000

Je vous remercie
la source
24
Semble trop compliqué (je suis particulièrement confus avec g => g(g)(x)). Y a-t-il un avantage à une fonction d'ordre supérieur par rapport à une fonction de premier ordre, comme dans ma solution?
Pavlo
1
@naomik: merci d'avoir pris le temps de publier un lien. très appréciée.
Pineda
1
@ AlfonsoPérez J'apprécie la remarque. Je vais voir si je peux travailler un petit indice là-dedans quelque part ^ _ ^
Merci
1
@naomik Adieu TCO ! Je suis dévasté.
10
Il semble que cette réponse soit acceptée et bien notée car elle a dû demander beaucoup d'efforts, mais je ne pense pas que ce soit une bonne réponse. La bonne réponse à la question est «non». Il est utile de répertorier une solution de contournement comme vous l'avez fait, mais juste après cela, vous déclarez qu'il existe un meilleur moyen. Pourquoi ne pas simplement mettre cette réponse et supprimer la pire en haut? Pourquoi expliquez-vous les opérateurs de virgule? Pourquoi évoquez-vous Clojure? Pourquoi, en général, autant de tangentes pour une question avec une réponse à 2 caractères? Les questions simples ne sont pas seulement une plate-forme permettant aux utilisateurs de faire une présentation sur certains faits de programmation intéressants.
Timofey 'Sasha' Kondrashov le
267

Utilisation de l' opérateur Spread ES2015 :

[...Array(n)].map()

const res = [...Array(10)].map((_, i) => {
  return i * 10;
});

// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);

Ou si vous n'avez pas besoin du résultat:

[...Array(10)].forEach((_, i) => {
  console.log(i);
});

// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));

Ou en utilisant l' opérateur ES2015 Array.from :

Array.from(...)

const res = Array.from(Array(10)).map((_, i) => {
  return i * 10;
});

// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);

Notez que si vous avez juste besoin d'une chaîne répétée, vous pouvez utiliser String.prototype.repeat .

console.log("0".repeat(10))
// 0000000000
Attache-moi
la source
26
Mieux:Array.from(Array(10), (_, i) => i*10)
Bergi
6
Cela devrait être la meilleure réponse. Alors ES6! Beaucoup génial!
Gergely Fehérvári
3
Si vous n'avez pas besoin de l'itérateur (i), vous pouvez exclure à la fois la clé et la valeur pour faire ceci:[...Array(10)].forEach(() => console.log('looping 10 times');
Sterling Bourne
9
Donc, vous allouez un tableau entier de N éléments juste pour le jeter?
Kugel
2
Quelqu'un at-il répondu au commentaire précédent de Kugel? Je me demandais la même chose
Arman
37
for (let i of Array(100).keys()) {
    console.log(i)
}
zerkms
la source
Cela fonctionne, donc c'est génial! Mais c'est un peu moche dans le sens où un travail supplémentaire est nécessaire et ce n'est pas pour cela que les Arrayclés sont utilisées.
à.
@à. En effet. Mais je ne suis pas sûr qu'il y ait un synonyme de haskell pour [0..x]dans JS plus concis que dans ma réponse.
zerkms
vous avez peut-être raison de dire qu'il n'y a rien de plus concis que cela.
à.
OK, je comprends pourquoi cela fonctionne compte tenu des différences entre Array.prototype.keyset Object.prototype.keys, mais c'est certainement déroutant à première vue.
Mark Reed
1
@cchamberlain avec TCO dans ES2015 (pas implémenté nulle part cependant?) Cela pourrait être le moins préoccupant, mais en effet :-)
zerkms
29

Je pense que la meilleure solution est d'utiliser let:

for (let i=0; i<100; i++) 

Cela créera une nouvelle ivariable (mutable) pour chaque évaluation de corps et assurera que le in'est changé que dans l'expression d'incrémentation dans cette syntaxe de boucle, pas de n'importe où ailleurs.

Je pourrais en quelque sorte tricher et créer mon propre générateur. Au moins i++est hors de vue :)

Cela devrait suffire à mon humble avis. Même dans les langages purs, toutes les opérations (ou du moins, leurs interpréteurs) sont construites à partir de primitives qui utilisent la mutation. Tant qu'il est correctement défini, je ne vois pas ce qui ne va pas avec cela.

Tu devrais être bien avec

function* times(n) {
  for (let i = 0; i < x; i++)
    yield i;
}
for (const i of times(5))
  console.log(i);

Mais je ne veux pas utiliser l' ++opérateur ou avoir des variables mutables du tout.

Ensuite, votre seul choix est d'utiliser la récursivité. Vous pouvez également définir cette fonction de générateur sans mutable i:

function* range(i, n) {
  if (i >= n) return;
  yield i;
  return yield* range(i+1, n);
}
times = (n) => range(0, n);

Mais cela me semble exagéré et pourrait avoir des problèmes de performances (car l'élimination des appels de queue n'est pas disponible pour return yield*).

Bergi
la source
1
J'aime cette option - agréable et simple!
DanV
2
C'est simple et pertinent et n'alloue pas un tableau comme beaucoup de réponses ci
Kugel
@Kugel Le deuxième pourrait cependant allouer sur la pile
Bergi
Bon point je ne sais pas si l'optimisation des appels finaux fonctionnera ici @Bergi
Kugel
13
const times = 4;
new Array(times).fill().map(() => console.log('test'));

Cet extrait sera console.log test4 fois.

Hossam Mourad
la source
Quel est le support pour le remplissage?
Aamir Afridi
2
@AamirAfridi Vous pouvez vérifier la section de compatibilité du navigateur, il y a aussi un polyfill fourni: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Hossam Mourad
12

Je pense que c'est assez simple:

[...Array(3).keys()]

ou

Array(3).fill()
Gergely Fehérvári
la source
11

Réponse: 9 décembre 2015

Personnellement, j'ai trouvé la réponse acceptée à la fois concise (bonne) et laconique (mauvaise). Je comprends que cette déclaration peut être subjective, alors veuillez lire cette réponse et voir si vous êtes d'accord ou pas d'accord

L'exemple donné dans la question ressemblait à celui de Ruby:

x.times do |i|
  do_stuff(i)
end

L'exprimer dans JS en utilisant ci-dessous permettrait:

times(x)(doStuff(i));

Voici le code:

let times = (n) => {
  return (f) => {
    Array(n).fill().map((_, i) => f(i));
  };
};

C'est tout!

Exemple d'utilisation simple:

let cheer = () => console.log('Hip hip hooray!');

times(3)(cheer);

//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!

Sinon, en suivant les exemples de réponse acceptée:

let doStuff = (i) => console.log(i, ' hi'),
  once = times(1),
  twice = times(2),
  thrice = times(3);

once(doStuff);
//0 ' hi'

twice(doStuff);
//0 ' hi'
//1 ' hi'

thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'

Note d'accompagnement - Définition d'une fonction de plage

Une question similaire / connexe, qui utilise des constructions de code fondamentalement très similaires, pourrait être l'existence d'une fonction Range pratique dans JavaScript (de base), quelque chose de similaire à la fonction range du soulignement.

Créer un tableau avec n nombres, à partir de x

Souligner

_.range(x, x + n)

ES2015

Quelques alternatives:

Array(n).fill().map((_, i) => x + i)

Array.from(Array(n), (_, i) => x + i)

Démo utilisant n = 10, x = 1:

> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

Dans un test rapide que j'ai effectué, chacun des éléments ci-dessus fonctionnant un million de fois chacun en utilisant notre solution et la fonction doStuff, la première approche (Array (n) .fill ()) s'est avérée légèrement plus rapide.

Arcseldon
la source
8
Array(100).fill().map((_,i)=> console.log(i) );

Cette version satisfait à l'exigence d'immuabilité de l'OP. Pensez également à utiliser reduceau lieu de map dépendre de votre cas d'utilisation.

C'est également une option si cela ne vous dérange pas une petite mutation dans votre prototype.

Number.prototype.times = function(f) {
   return Array(this.valueOf()).fill().map((_,i)=>f(i));
};

Maintenant on peut faire ça

((3).times(i=>console.log(i)));

+1 à arcseldon pour la .fillsuggestion.

À M
la source
Voté vers le bas, car la méthode de remplissage n'est pas prise en charge dans IE, Opera ou PhantomJS
morhook
8

Voici une autre bonne alternative:

Array.from({ length: 3}).map(...);

De préférence, comme @Dave Morse l'a souligné dans les commentaires, vous pouvez également vous débarrasser de l' mapappel, en utilisant le deuxième paramètre de la Array.fromfonction comme ceci:

Array.from({ length: 3 }, () => (...))
oemera
la source
2
Cela devrait être la réponse acceptée! Une petite suggestion - vous obtenez déjà gratuitement la fonctionnalité de type carte dont vous avez besoin avec Array.from: Array.from({ length: label.length }, (_, i) => (...)) cela évite de créer un tableau temporaire vide juste pour lancer un appel à la carte.
Dave Morse le
7

Ce n'est pas quelque chose que j'enseignerais (ou que j'utiliserais jamais dans mon code), mais voici une solution digne de codegolf sans muter une variable, pas besoin d'ES6:

Array.apply(null, {length: 10}).forEach(function(_, i){
    doStuff(i);
})

Plus une chose intéressante de preuve de concept qu'une réponse utile, vraiment.

doldt
la source
N'est-ce Array.apply(null, {length: 10})pas juste Array(10)?
Pavlo
1
@Pavlo, en fait, non. Array (10) créerait un tableau de longueur 10, mais sans aucune clé définie, ce qui rend la construction forEach inutilisable dans ce cas. Mais en effet, cela peut être simplifié si vous n'utilisez pas forEach, voir la réponse de zerkms (qui utilise cependant ES6!).
doldt
creative @doldt, mais je recherche quelque chose de simple et d'apprentissage.
à.
5

Je suis en retard à la fête, mais comme cette question revient souvent dans les résultats de recherche, je voudrais juste ajouter une solution que je considère comme la meilleure en termes de lisibilité sans être longue (ce qui est idéal pour toute base de code IMO) . Il mute, mais je ferais ce compromis pour les principes de KISS.

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0
J Garcia
la source
3
Merci d'être la voix de la raison dans ce que je ne peux décrire que comme une fête fétiche lambda d'ordre supérieur. Moi aussi, je me suis retrouvé sur ce Q&A en suivant un premier coup inoffensif sur Google et j'ai rapidement profané ma santé mentale par la plupart des réponses ici. Le vôtre est le premier de la liste que je considérerais comme une solution simple à un problème simple.
Martin Devillers le
Le seul problème avec cela est que c'est un peu contre-intuitif si vous souhaitez utiliser la timesvariable à l'intérieur de la boucle. Ce countdownserait peut -être un meilleur nom. Sinon, réponse la plus claire et la plus claire sur la page.
Tony Brasunas
3

Afaik, il n'y a pas de mécanisme dans ES6 similaire à la timesméthode de Ruby . Mais vous pouvez éviter la mutation en utilisant la récursivité:

let times = (i, cb, l = i) => {
  if (i === 0) return;

  cb(l - i);
  times(i - 1, cb, l);
}

times(5, i => doStuff(i));

Démo: http://jsbin.com/koyecovano/1/edit?js,console

Pavlo
la source
J'aime cette approche, j'adore la récursivité. Mais j'aimerais quelque chose de plus simple pour montrer les boucles des nouveaux utilisateurs JavaScript.
à.
3

Si vous êtes prêt à utiliser une bibliothèque, il existe également un lodash_.times ou un soulignement_.times :

_.times(x, i => {
   return doStuff(i)
})

Notez que cela renvoie un tableau des résultats, donc c'est vraiment plus comme ce rubis:

x.times.map { |i|
  doStuff(i)
}
Ronen
la source
2

Dans le paradigme fonctionnel repeatest généralement une fonction récursive infinie. Pour l'utiliser, nous avons besoin d'une évaluation paresseuse ou d'un style de passage continu.

Répétition de fonction évaluée paresseuse

const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

J'utilise un thunk (une fonction sans arguments) pour obtenir une évaluation paresseuse en Javascript.

Répétition de fonction avec style de passage de continuation

const repeat = f => x => [x, k => k(repeat(f) (f(x)))];
const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1));

console.log(
  take(8) (repeat(x => x * 2) (1)) // 256
);

CPS est un peu effrayant au début. Cependant, il suit toujours le même schéma: Le dernier argument est la continuation (fonction), qui invoque son propre corps: k => k(...). Veuillez noter que CPS transforme l'application à l'envers, c'est-à-dire take(8) (repeat...)devient k(take(8)) (...)là où kest partiellement appliquée repeat.

Conclusion

En séparant la répétition ( repeat) de la condition de terminaison ( take), nous gagnons en flexibilité - séparation des préoccupations jusqu'à sa fin amère: D


la source
1

Avantages de cette solution

  • Le plus simple à lire / utiliser (imo)
  • La valeur de retour peut être utilisée comme une somme ou simplement ignorée
  • Version simple es6, également lien vers la version TypeScript du code

Inconvénients - Mutation. Étant seulement interne, je m'en fiche, peut-être que d'autres non plus.

Exemples et code

times(5, 3)                       // 15    (3+3+3+3+3)

times(5, (i) => Math.pow(2,i) )   // 31    (1+2+4+8+16)

times(5, '<br/>')                 // <br/><br/><br/><br/><br/>

times(3, (i, count) => {          // name[0], name[1], name[2]
    let n = 'name[' + i + ']'
    if (i < count-1)
        n += ', '
    return n
})

function times(count, callbackOrScalar) {
    let type = typeof callbackOrScalar
    let sum
    if (type === 'number') sum = 0
    else if (type === 'string') sum = ''

    for (let j = 0; j < count; j++) {
        if (type === 'function') {
            const callback = callbackOrScalar
            const result = callback(j, count)
            if (typeof result === 'number' || typeof result === 'string')
                sum = sum === undefined ? result : sum + result
        }
        else if (type === 'number' || type === 'string') {
            const scalar = callbackOrScalar
            sum = sum === undefined ? scalar : sum + scalar
        }
    }
    return sum
}

Version TypeScipt
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011

Whitneyland
la source
0

aborder l'aspect fonctionnel:

function times(n, f) {
    var _f = function (f) {
        var i;
        for (i = 0; i < n; i++) {
            f(i);
        }
    };
    return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
    console.log('in parts: ' + v);
});
times(6, function (v) {
    console.log('complete: ' + v);
});
Nina Scholz
la source
5
"adresser l'aspect fonctionnel" puis utiliser une boucle impérative avec un mutable i. Quelle est la raison de même utiliser timesplus de vieux foralors?
zerkms
réutiliser comme var twice = times(2);.
Nina Scholz
Alors pourquoi ne pas utiliser fordeux fois?
zerkms
je n'ai pas peur d'utiliser pour. la question était de ne pas utiliser un variabele. mais le résultat est toujours une sorte de cache aka variable.
Nina Scholz
1
"était quelque chose de ne pas utiliser un variabele" --- et vous l'utilisez toujours - i++. Il n'est pas évident de savoir comment intégrer quelque chose d'inacceptable dans une fonction l'améliore.
zerkms
0

Générateurs? La récursivité? Pourquoi tant de haine sur la mutation? ;-)

Si c'est acceptable tant que nous le «cachons», alors acceptez simplement l'utilisation d'un opérateur unaire et nous pouvons garder les choses simples :

Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }

Tout comme dans le rubis:

> (3).times(console.log)
0
1
2
conny
la source
2
Bravo: "Pourquoi tant de haine sur la mutation?"
Sarreph
1
Bravo pour la simplicité, bravo pour aller un peu trop dans le style rubis avec le monkeypatch. Dis simplement non à ces méchants singes.
mrm
1
@mrm est-ce "monkey patching", n'est-ce pas juste un cas d'extension? Embrace & extend :)
conny
Non. L'ajout de fonctions à Number (ou String ou Array ou à toute autre classe que vous n'avez pas créée) sont, par définition, des polyfills ou des monkey patches - et même les polyfills ne sont pas recommandés. Lisez les définitions de «monkey patch», «polyfill» et une alternative recommandée, «ponyfill». C'est ce que tu veux.
mrm
Pour étendre Number, vous feriez: class SuperNumber extend Number {times (fn) {for (let i = 0; i <this; i ++) {fn (i); }}}
Alexander
0

J'ai enveloppé la réponse de @Tieme avec une fonction d'assistance.

Dans TypeScript:

export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())

Vous pouvez maintenant exécuter:

const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
Andries
la source
0

J'ai fabriqué ça:

function repeat(func, times) {
    for (var i=0; i<times; i++) {
        func(i);
    }
}

Usage:

repeat(function(i) {
    console.log("Hello, World! - "+i);
}, 5)

/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/

La ivariable renvoie le nombre de fois qu'elle a bouclé - utile si vous avez besoin de précharger une quantité x d'images.

Nanoo
la source