Gibt es einen Mechanismus, um in ES6 (ECMAScript 6) x-mal ohne veränderbare Variablen zu schleifen?


154

Die typische Methode zum Schleifen von xZeiten in JavaScript ist:

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

Aber ich möchte den ++Operator nicht verwenden oder überhaupt keine veränderlichen Variablen haben. Gibt es in ES6 eine Möglichkeit, xZeiten auf eine andere Weise zu wiederholen ? Ich liebe Rubys Mechanismus:

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

Ähnliches in JavaScript / ES6? Ich könnte irgendwie schummeln und meinen eigenen Generator bauen:

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

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

Natürlich benutze ich immer noch i++. Zumindest ist es außer Sicht :), aber ich hoffe, dass es in ES6 einen besseren Mechanismus gibt.


3
Warum ist die veränderbare Regelungsvariable ein Problem? Nur ein Prinzip?
Doldt

1
@doldt - Ich versuche , JavaScript zu unterrichten, aber ich experimentiere mit Verzögerung des Konzepts der änderbaren Variablen erst später
an.

5
Wir kommen hier wirklich vom Thema ab, aber sind Sie sicher, dass die Umstellung auf ES6-Generatoren (oder ein anderes neues Konzept auf hoher Ebene) eine gute Idee ist, bevor sie etwas über veränderbare Variablen erfahren? :)
doldt

5
@oldt - vielleicht experimentiere ich. Ein funktionaler Sprachansatz für JavaScript.
um.

Antworten:


156

OK!

Der folgende Code wurde mit ES6-Syntax geschrieben, kann aber genauso gut in ES5 oder noch weniger geschrieben werden. ES6 ist nicht erforderlich, um einen "Mechanismus zum x-maligen Schleifen" zu erstellen.


Wenn Sie den Iterator im Rückruf nicht benötigen , ist dies die einfachste Implementierung

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'))

Wenn Sie den Iterator benötigen , können Sie eine benannte innere Funktion mit einem Zählerparameter verwenden, um für Sie zu iterieren

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'))


Hör hier auf zu lesen, wenn du nicht gerne mehr lernst ...

Aber etwas sollte sich an diesen ...

  • Einzelne Verzweigungsanweisungen ifsind hässlich - was passiert auf der anderen Verzweigung?
  • mehrere Anweisungen / Ausdrücke in den Funktionskörpern - werden Prozedurprobleme gemischt?
  • implizit zurückgegeben undefined- Hinweis auf eine unreine Nebenwirkung

"Gibt es keinen besseren Weg?"

Es gibt. Lassen Sie uns zunächst unsere erste Implementierung erneut betrachten

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

Sicher, es ist einfach, aber beachten Sie, wie wir einfach anrufen f()und nichts damit anfangen. Dies schränkt die Art der Funktion, die wir mehrmals wiederholen können, wirklich ein. Selbst wenn wir den Iterator zur Verfügung haben, f(i)ist er nicht viel vielseitiger.

Was ist, wenn wir mit einer besseren Art der Funktionswiederholung beginnen? Vielleicht etwas, das Input und Output besser nutzt.

Generische Funktionswiederholung

// 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

Oben haben wir eine generische repeatFunktion definiert, die eine zusätzliche Eingabe benötigt, mit der die wiederholte Anwendung einer einzelnen Funktion gestartet wird.

// 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)))

Implementierung timesmitrepeat

Nun, das ist jetzt einfach. Fast die gesamte Arbeit ist bereits erledigt.

// 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'))

Da unsere Funktion ials Eingabe verwendet und zurückkehrt i + 1, funktioniert dies effektiv als unser Iterator, an den wir übergebenf jedes Mal übergeben.

Wir haben auch unsere Aufzählungsliste mit Problemen behoben

  • Kein hässlicher Zweig mehr if Aussagen
  • Körper mit einfachem Ausdruck weisen auf gut getrennte Bedenken hin
  • Nicht mehr nutzlos, implizit zurückgegeben undefined

JavaScript-Kommaoperator, der

Falls Sie Probleme haben zu sehen, wie das letzte Beispiel funktioniert, hängt es davon ab, ob Sie eine der ältesten Kampfachsen von JavaScript kennen. der Kommaoperator - kurz gesagt, er wertet Ausdrücke von links nach rechts aus und gibt den Wert des zuletzt ausgewerteten Ausdrucks zurück

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

In unserem obigen Beispiel verwende ich

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

Das ist nur eine prägnante Art zu schreiben

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

Tail Call-Optimierung

So sexy die rekursiven Implementierungen auch sind, an diesem Punkt wäre es für mich unverantwortlich, sie zu empfehlen, da keine JavaScript-VM, die ich mir vorstellen kann, die ordnungsgemäße Eliminierung von Tail Calls unterstützt - Babel, das zum Transpilieren verwendet wird, aber in "defekt; wird neu implementiert" "Status für weit über ein Jahr.

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

Als solches sollten wir unsere Implementierung von überdenken repeat , um sie stapelsicher zu machen.

Der folgende Code tut verwenden änderbare Variablen nund xaber zur Kenntnis , dass alle Mutationen auf die lokalisierten repeatFunktion - keine staatlichen Veränderungen (Mutationen) sind sichtbar von außerhalb der Funktion

// 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

Viele von Ihnen werden sagen: "Aber das funktioniert nicht!" - Ich weiß, entspann dich einfach. Wir können einen Clojure-Stil loop/ eine Clojure- recurSchnittstelle für Konstantraumschleifen unter Verwendung reiner Ausdrücke implementieren . keines davonwhile .

Hier abstrahieren wir whilemit unserer loopFunktion - sie sucht nach einem speziellen recurTyp, um die Schleife am Laufen zu halten. Wenn ein Nicht- recurTyp angetroffen wird, ist die Schleife beendet und das Ergebnis der Berechnung wird zurückgegeben

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


24
Scheint überkompliziert (ich bin besonders verwirrt mit g => g(g)(x)). Gibt es einen Vorteil einer Funktion höherer Ordnung gegenüber einer Funktion erster Ordnung, wie in meiner Lösung?
Pavlo

1
@naomik: danke, dass du dir die Zeit genommen hast, einen Link zu posten. sehr geschätzt.
Pineda

1
@ AlfonsoPérez Ich schätze die Bemerkung. Ich werde sehen, ob ich dort irgendwo einen kleinen Hinweis einarbeiten kann ^ _ ^
Danke

1
@naomik Farewell TCO ! Ich bin am Boden zerstört.

9
Es scheint, dass diese Antwort akzeptiert und gut bewertet wird, weil es viel Mühe gekostet haben muss, aber ich denke nicht, dass es eine gute Antwort ist. Die richtige Antwort auf die Frage lautet "nein". Es ist hilfreich, eine Problemumgehung wie Sie aufzulisten, aber gleich danach geben Sie an, dass es einen besseren Weg gibt. Warum setzen Sie diese Antwort nicht einfach ein und entfernen die schlechtere oben? Warum erklären Sie Kommaoperatoren? Warum erzählst du Clojure? Warum im Allgemeinen so viele Tangenten für eine Frage mit einer 2-stelligen Antwort? Einfache Fragen sind nicht nur eine Plattform für Benutzer, auf der sie einige nützliche Programmierfakten präsentieren können.
Timofey 'Sasha' Kondrashov

264

Verwenden des Spread-Operators 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);

Oder wenn Sie das Ergebnis nicht brauchen:

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

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

Oder verwenden Sie den Operator 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);

Beachten Sie, dass Sie String.prototype.repeat verwenden können, wenn Sie nur eine Zeichenfolge wiederholen müssen .

console.log("0".repeat(10))
// 0000000000

26
Besser:Array.from(Array(10), (_, i) => i*10)
Bergi

6
Dies sollte die beste Antwort sein. Also ES6! Sehr genial!
Gergely Fehérvári

3
Wenn Sie den Iterator (i) nicht benötigen, können Sie sowohl den Schlüssel als auch den Wert ausschließen, um dies zu tun:[...Array(10)].forEach(() => console.log('looping 10 times');
Sterling Bourne

8
Sie weisen also ein ganzes Array von N Elementen zu, um es wegzuwerfen?
Kugel

2
Hat jemand den vorherigen Kommentar von Kugel angesprochen? Ich habe mich das Gleiche gefragt
Arman

37
for (let i of Array(100).keys()) {
    console.log(i)
}

Das funktioniert, das ist großartig! Aber es ist ein bisschen hässlich in dem Sinne, dass zusätzliche Arbeit erforderlich ist und dafür nicht die ArraySchlüssel verwendet werden.
um.

@beim. tatsächlich. Aber ich bin mir nicht sicher, ob es [0..x]in JS ein Haskell-Synonym gibt, das prägnanter ist als in meiner Antwort.
Zerkms

Sie könnten richtig sein, dass es nichts prägnanteres gibt als dies.
um.

OK, ich verstehe, warum dies angesichts der Unterschiede zwischen Array.prototype.keysund funktioniert Object.prototype.keys, aber es ist auf den ersten Blick verwirrend.
Mark Reed

1
@cchamberlain mit TCO in ES2015 (aber nirgendwo implementiert?) könnte es weniger besorgniserregend sein, aber in der Tat :-)
zerkms

29

Ich denke, die beste Lösung ist let:

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

Dadurch wird ifür jede Körperbewertung eine neue (veränderbare) Variable erstellt, und es wird sichergestellt, dass die Variable inur im Inkrementausdruck in dieser Schleifensyntax geändert wird, nicht von irgendwo anders.

Ich könnte irgendwie schummeln und meinen eigenen Generator bauen. Zumindest i++ist außer Sicht :)

Das sollte imo reichen. Selbst in reinen Sprachen bestehen alle Operationen (oder zumindest ihre Interpreten) aus Grundelementen, die Mutationen verwenden. Solange es richtig ist, kann ich nicht sehen, was daran falsch ist.

Du solltest in Ordnung sein mit

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

Aber ich möchte den ++Operator nicht verwenden oder überhaupt keine veränderlichen Variablen haben.

Dann besteht Ihre einzige Wahl darin, die Rekursion zu verwenden. Sie können diese Generatorfunktion auch ohne veränderliche Funktion definieren i:

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

Aber das scheint mir übertrieben und könnte Leistungsprobleme haben (da die Eliminierung von Tail Calls nicht verfügbar ist return yield*).


1
Ich mag diese Option - schön und einfach!
DanV

2
Dies ist einfach und auf den Punkt und ordnet kein Array wie viele Antworten oben
Kugel

@ Kugel Der zweite könnte jedoch auf dem Stapel
zuordnen

Guter Punkt nicht sicher, ob Tail-Call-Optimierung hier funktioniert @Bergi
Kugel



11

Antwort: 09. Dezember 2015

Persönlich fand ich die akzeptierte Antwort sowohl prägnant (gut) als auch knapp (schlecht). Schätzen Sie, dass diese Aussage möglicherweise subjektiv ist. Lesen Sie daher diese Antwort und prüfen Sie, ob Sie zustimmen oder nicht

Das Beispiel in der Frage war so etwas wie Rubys:

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

Wenn Sie dies in JS mit den folgenden Angaben ausdrücken, können Sie Folgendes tun:

times(x)(doStuff(i));

Hier ist der Code:

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

Das ist es!

Einfache Beispielverwendung:

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

times(3)(cheer);

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

Alternativ folgen Sie den Beispielen der akzeptierten Antwort:

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'

Randnotiz - Definieren einer Bereichsfunktion

Eine ähnliche / verwandte Frage, die grundsätzlich sehr ähnliche Codekonstrukte verwendet, könnte sein, dass es in (Kern-) JavaScript eine praktische Bereichsfunktion gibt, die der Bereichsfunktion des Unterstrichs ähnelt.

Erstellen Sie ein Array mit n Zahlen, beginnend mit x

Unterstreichen

_.range(x, x + n)

ES2015

Einige Alternativen:

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

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

Demo mit 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 ]

In einem schnellen Test, den ich mit unserer Lösung und der doStuff-Funktion jeweils millionenfach ausgeführt habe, hat sich der frühere Ansatz (Array (n) .fill ()) als etwas schneller erwiesen.


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

Diese Version erfüllt die Anforderungen des OP an die Unveränderlichkeit. Erwägen Sie auch die Verwendung von reduceanstelle vonmap abhängig von Ihrem Anwendungsfall.

Dies ist auch eine Option, wenn Ihnen eine kleine Mutation in Ihrem Prototyp nichts ausmacht.

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

Jetzt können wir das tun

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

+1 an arcseldon für den .fillVorschlag.


Abgestimmt, da die Füllmethode in IE, Opera oder PhantomJS nicht unterstützt wird
morhook

7

Nicht etwas, das ich lehren würde (oder jemals in meinem Code verwenden würde), aber hier ist eine Codegolf-würdige Lösung, ohne eine Variable zu mutieren, ohne dass ES6 erforderlich ist:

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

Eigentlich eher eine interessante Proof-of-Concept-Sache als eine nützliche Antwort.


Könnte Array.apply(null, {length: 10})nicht einfach sein Array(10)?
Pavlo

1
@Pavlo, eigentlich nein. Array (10) würde ein Array mit der Länge 10 erstellen, jedoch ohne darin definierte Schlüssel, wodurch das forEach-Konstrukt in diesem Fall nicht verwendet werden kann. Aber in der Tat kann es vereinfacht werden, wenn Sie nicht für jeden verwenden, siehe die Antwort von zerkms (allerdings wird ES6 verwendet!).
Doldt

kreativ @doldt, aber ich suche etwas Lehrbares und Einfaches.
um.

7

Hier ist eine weitere gute Alternative:

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

Wie @Dave Morse in den Kommentaren hervorhob, können Sie den mapAufruf vorzugsweise auch entfernen , indem Sie den zweiten Parameter der Array.fromFunktion wie folgt verwenden:

Array.from({ length: 3 }, () => (...))


1
Dies sollte die akzeptierte Antwort sein! Ein kleiner Vorschlag: Mit Array.from erhalten Sie bereits die kartenähnliche Funktionalität, die Sie kostenlos benötigen: Array.from({ length: label.length }, (_, i) => (...)) Dies erspart das Erstellen eines leeren temporären Arrays, um einen Aufruf der Karte zu starten.
Dave Morse

4

Ich komme zu spät zur Party, aber da diese Frage in den Suchergebnissen häufig auftaucht, möchte ich nur eine Lösung hinzufügen, die meiner Meinung nach die beste Lesbarkeit ist, wenn sie nicht lang ist (ideal für jede Codebasis-IMO). . Es mutiert, aber ich würde diesen Kompromiss für die KISS-Prinzipien eingehen.

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0

2
Vielen Dank, dass Sie die Stimme der Vernunft in einer Lambda-Fetisch-Party höherer Ordnung sind. Auch ich bin nach einem harmlosen ersten Treffer bei Google auf diese Frage und Antwort gekommen und hatte meine geistige Gesundheit durch die meisten Antworten hier schnell entweiht. Ihre ist die erste in der Liste, die ich als einfache Lösung für ein einfaches Problem betrachten würde.
Martin Devillers

3

Afaik, es gibt keinen Mechanismus in ES6, der Rubys timesMethode ähnelt . Sie können jedoch eine Mutation vermeiden, indem Sie die Rekursion verwenden:

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

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

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

Demo: http://jsbin.com/koyecovano/1/edit?js,console


Ich mag diesen Ansatz, ich liebe Rekursion. Aber ich würde etwas Einfacheres lieben, um neue JavaScript-Benutzerschleifen anzuzeigen.
um.

3

Wenn Sie bereit sind, eine Bibliothek zu nutzen, gibt es auch Lodash_.times oder Unterstrich_.times :

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

Beachten Sie, dass dies ein Array der Ergebnisse zurückgibt, also eher wie dieser Rubin:

x.times.map { |i|
  doStuff(i)
}

2

Im funktionalen Paradigma repeatist normalerweise eine unendliche rekursive Funktion. Um es zu verwenden, benötigen wir entweder eine verzögerte Bewertung oder einen Weitergabestil.

Lazy bewertete Funktionswiederholung

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
);

Ich benutze einen Thunk (eine Funktion ohne Argumente), um eine verzögerte Auswertung in Javascript zu erreichen.

Funktionswiederholung mit Fortsetzungsstil

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 ist zunächst etwas beängstigend. Es folgt jedoch immer dem gleichen Muster: Das letzte Argument ist die Fortsetzung (eine Funktion), die ihren eigenen Körper aufruft : k => k(...). Bitte beachten Sie, dass CPS die Anwendung von innen nach außen dreht, also take(8) (repeat...)wird , k(take(8)) (...)wo kdie teilweise angewandt repeat.

Fazit

Durch die Trennung der Wiederholung ( repeat) von der Beendigungsbedingung ( take) erhalten wir Flexibilität - Trennung von Bedenken bis zu ihrem bitteren Ende: D.


1

Vorteile dieser Lösung

  • Am einfachsten zu lesen / zu verwenden (imo)
  • Der Rückgabewert kann als Summe verwendet oder einfach ignoriert werden
  • Einfache es6-Version, auch Link zur TypeScript-Version des Codes

Nachteile - Mutation. Nur intern zu sein ist mir egal, vielleicht werden es auch einige andere nicht.

Beispiele und 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
}

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


0

Adressierung des funktionalen Aspekts:

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);
});

5
"Adressierung des funktionalen Aspekts" und anschließende Verwendung der Imperativschleife mit einer veränderlichen i. Was ist der Grund, um dann timesüber einfach alt zu verwenden for?
Zerkms

wiederverwenden wie var twice = times(2);.
Nina Scholz

Warum also nicht einfach forzweimal verwenden?
Zerkms

Ich habe keine Angst, für zu verwenden. Die Frage war, keine Variable zu verwenden. Das Ergebnis ist jedoch immer eine Art Caching, auch bekannt als Variable.
Nina Scholz

1
"war etwas, um keine Variable zu verwenden" --- und Sie verwenden es immer noch - i++. Es ist nicht offensichtlich, wie es besser ist, etwas inakzeptables in eine Funktion zu packen.
Zerkms

0

Generatoren? Rekursion? Warum so viel Mutatin? ;-);

Wenn es akzeptabel ist, solange wir es "verstecken", akzeptieren Sie einfach die Verwendung eines unären Operators und wir können die Dinge einfach halten :

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

Genau wie in Rubin:

> (3).times(console.log)
0
1
2

2
Daumen hoch: "Warum so viel Mutatin?"
Sarreph

1
Der Einfachheit halber Daumen hoch, Daumen runter, um mit dem Monkeypatch ein bisschen zu viel Rubin zu machen. Sag einfach nein zu diesen bösen Affen.
Herr

1
@mrm ist das "Affen-Patching", ist das nicht nur ein Fall von Erweiterung? Umarmen & verlängern :)
conny

Nein. Das Hinzufügen von Funktionen zu Number (oder String oder Array oder einer anderen Klasse, die Sie nicht verfasst haben) sind per Definition entweder Polyfills oder Affen-Patches - und selbst Polyfills werden nicht empfohlen. Lesen Sie die Definitionen von "Affenpflaster", "Polyfill" und eine empfohlene Alternative, "Ponyfill". Das ist, was du willst.
Herr

Um Number zu erweitern, würden Sie Folgendes tun: class SuperNumber erweitert Number {times (fn) {for (sei i = 0; i <this; i ++) {fn (i); }}}
Alexander

0

Ich habe die Antwort von @Tieme mit einer Hilfsfunktion versehen.

In TypeScript:

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

Jetzt können Sie ausführen:

const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.