wake-up-neo.com

javascript: rekursive anonyme Funktion?

Nehmen wir an, ich habe eine grundlegende rekursive Funktion:

function recur(data) {
    data = data+1;
    var nothing = function() {
        recur(data);
    }
    nothing();
}

Wie kann ich das machen, wenn ich eine anonyme Funktion wie ...

(function(data){
    data = data+1;
    var nothing = function() {
        //Something here that calls the function?
    }
    nothing();
})();

Ich möchte eine Möglichkeit, die Funktion aufzurufen, die diese Funktion aufgerufen hat ... Ich habe irgendwo Skripte gesehen (ich kann mich nicht erinnern, wo), die Ihnen den Namen einer aufgerufenen Funktion nennen können, aber ich kann mich an keine erinnern diese Informationen jetzt.

106
Incognito

Sie can geben der Funktion einen Namen, auch wenn Sie die Funktion als Wert und nicht als "Funktionsdeklaration" erstellen. Mit anderen Worten:

(function foo() { foo(); })();

ist eine rekursive Stackblowing-Funktion. Nun, das sagte, du  wahrscheinlich nicht kann dies nicht tun im Allgemeinen, weil es einige seltsame Probleme mit verschiedenen Implementierungen von Javascript gibt. (note - das ist ein ziemlich alter Kommentar; einige/viele/alle Probleme, die in Kangaxs Blogbeitrag beschrieben werden, können in moderneren Browsern behoben werden.)

Wenn Sie einen solchen Namen angeben, ist der Name außerhalb der Funktion nicht sichtbar (es sollte nicht so sein; das ist eine der Verrückten). Es ist wie "letrec" in LISP.

Was arguments.callee anbelangt, ist dies im "strikten" Modus nicht zulässig und wird im Allgemeinen als schlecht angesehen, da es einige Optimierungen erschwert. Es ist auch viel langsamer als man erwarten könnte.

edit - Wenn Sie die Wirkung einer "anonymen" Funktion haben möchten, die sich selbst aufrufen kann, können Sie Folgendes tun (vorausgesetzt, Sie übergeben die Funktion als Rückruf oder ähnliches):

asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());

Was dies bedeutet, definieren Sie eine Funktion mit einer Nice-sicheren, nicht-defekten IE-Funktion Deklaration -Anweisung, die eine lokale Funktion erstellt, deren Name den globalen Namensraum nicht verschmutzt. Die Wrapper-Funktion (wirklich anonym) gibt nur diese lokale Funktion zurück.

131
Pointy

Die Leute sprachen in Kommentaren über den Y Combinator, aber niemand schrieb es als Antwort.

Der Y-Combinator kann in Javascript wie folgt definiert werden: (Danke an steamer25 für den Link)

var Y = function (gen) {
  return (function(f) {
    return f(f);
  }(function(f) {
    return gen(function() {
      return f(f).apply(null, arguments);
    });
  }));
}

Und wenn Sie Ihre anonyme Funktion übergeben möchten:

(Y(function(recur) {
  return function(data) {
    data = data+1;
    var nothing = function() {
      recur(data);
    }
    nothing();
  }
})());

Das Wichtigste an dieser Lösung ist, dass Sie sie nicht verwenden sollten.

30
zem

Ich würde dies nicht als Inline-Funktion tun. Es stößt gegen die Grenzen des guten Geschmacks und bringt dir wirklich nichts.

Wenn Sie wirklich müssen, gibt es arguments.callee wie in Fabrizios Antwort. Dies wird jedoch im Allgemeinen als nicht ratsam angesehen und ist im "strikten Modus" von ECMAScript Fifth Edition nicht zulässig. Obwohl ECMA 3 und Non-Strict-Modus nicht verschwinden, verspricht das Arbeiten im strikten Modus mehr Sprachoptimierungen.

Man kann auch eine benannte Inline-Funktion verwenden:

(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();

Benannte Inline-Funktionsausdrücke werden jedoch auch am besten vermieden, da das JScript von IE einige negative Auswirkungen auf sie hat. Im obigen Beispiel verschmutzt foo den übergeordneten Bereich in IE falsch und die übergeordnete foo ist eine separate Instanz der foo, die in foo zu sehen ist.

Was ist der Zweck, dies in eine anonyme Inline-Funktion zu setzen? Wenn Sie nur verhindern möchten, dass der übergeordnete Bereich verschmutzt wird, können Sie Ihr erstes Beispiel natürlich in einer anderen selbstaufrufenden anonymen Funktion (Namespace) ausblenden. Müssen Sie wirklich jedes Mal um die Rekursion eine neue Kopie von nothing erstellen? Mit einem Namespace, der zwei einfache, sich gegenseitig rekursive Funktionen enthält, ist es möglicherweise besser.

13
bobince

Es kann am einfachsten sein, stattdessen ein "anonymes Objekt" zu verwenden:

({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

Ihr globaler Raum ist völlig unbelastet. Das ist ziemlich einfach. Und Sie können den nicht-globalen Status des Objekts problemlos nutzen.

12
svidgen

U-Kombinator

Der U-Kombinator übernimmt eine Funktion und wendet diese auf sich selbst an. Die Funktion, die Sie ihr geben, sollte mindestens einen Parameter haben, der an die Funktion (selbst) gebunden wird.

In dem folgenden Beispiel haben wir keine Beendigungsbedingung, so dass wir eine unendliche Schleife ausführen, bis ein Stapelüberlauf auftritt

const U = f => f (f)

U (f => (console.log ('stack overflow imminent!'), U (f)))

Wir können die unendliche Rekursion mit verschiedenen Techniken stoppen. Hier schreibe ich unsere anonyme Funktion, um eine andere anonyme Funktion zurückzugeben, die auf eine Eingabe wartet; in diesem Fall eine Anzahl. Wenn eine Zahl angegeben wird, die größer als 0 ist, werden wir fortfahren, andernfalls geben Sie 0 zurück.

const log = x => (console.log (x), x)

const U = f => f (f)

// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function

// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0

Was hier nicht sofort ersichtlich ist, ist, dass unsere Funktion, wenn sie zuerst mit dem U-Kombinator auf sich selbst angewendet wird, eine Funktion zurückgibt, die auf die erste Eingabe wartet. Wenn wir dem einen Namen gegeben haben, können Sie rekursive Funktionen mit Lambdas (anonyme Funktionen) effektiv erstellen.

const log = x => (console.log (x), x)

const U = f => f (f)

const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

Nur dies ist nicht direkt Rekursion - eine Funktion, die sich mit ihrem eigenen Namen aufruft. Unsere Definition von countDown bezieht sich nicht auf sich selbst und dennoch ist Rekursion möglich

// direct recursion references itself by name
const loop = (params) => {
  if (condition)
    return someValue
  else
    // loop references itself to recur...
    return loop (adjustedParams)
}

// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

So entfernen Sie den Selbstverweis aus einer vorhandenen Funktion mit dem U-Kombinator

Hier zeige ich Ihnen, wie Sie eine rekursive Funktion verwenden, die eine Referenz auf sich selbst verwendet, und sie in eine Funktion ändern, die den U-Kombinator anstelle der Selbstreferenz verwendet

const factorial = x =>
  x === 0 ? 1 : x * factorial (x - 1)
  
console.log (factorial (5)) // 120

Verwenden Sie nun den U-Kombinator, um die innere Referenz auf factorial zu ersetzen.

const U = f => f (f)

const factorial = U (f => x =>
  x === 0 ? 1 : x * U (f) (x - 1))

console.log (factorial (5)) // 120

Das grundlegende Ersatzmuster ist dieses. Machen Sie sich eine mentale Notiz, wir werden im nächsten Abschnitt eine ähnliche Strategie anwenden

// self reference recursion
const foo =         x => ...   foo (nextX) ...

// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)

Y-Kombinator

verwandt: die U- und Y-Kombinatoren anhand einer Spiegelanalogie erklärt

Im vorherigen Abschnitt haben wir gesehen, wie man die Rekursion der Selbstreferenz in eine rekursive Funktion umwandelt, die sich nicht auf eine benannte Funktion unter Verwendung des U-Kombinators stützt. Es ist ein bisschen ärgerlich, dass man daran denken muss, die Funktion immer als erstes Argument an sich selbst zu übergeben. Nun, der Y-Kombinator baut auf dem U-Kombinator auf und entfernt dieses langweilige Bit. Dies ist eine gute Sache, denn das Entfernen/Reduzieren der Komplexität ist der Hauptgrund, warum wir Funktionen erstellen

Lassen Sie uns zunächst unseren eigenen Y-Kombinator herleiten

// standard definition
const Y = f => f (Y (f))

// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))

// remove reference to self using U combinator
const Y = U (h => f => f (x =>U (h) (f) (x)))

Jetzt werden wir sehen, wie es im Vergleich zum U-Kombinator verwendet wird. Beachten Sie, um zu wiederholen, anstelle von U (f) können Sie einfach f () aufrufen.

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

Y (f => (console.log ('stack overflow imminent!'),  f ()))

Jetzt werde ich das Programm countDown mit Y demonstrieren - Sie werden sehen, dass die Programme fast identisch sind, aber der Y Combinator hält die Dinge ein wenig sauberer

const log = x => (console.log (x), x)

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

Und jetzt sehen wir auch factorial

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const factorial = Y (f => x =>
  x === 0 ? 1 :  x * f (x - 1))

console.log (factorial (5)) // 120


U und Y-Kombinator mit mehr als 1 Parameter

In den obigen Beispielen haben wir gesehen, wie wir eine Schleife bilden und ein Argument übergeben können, um den "Zustand" unserer Berechnung zu verfolgen. Was aber, wenn wir den zusätzlichen Zustand verfolgen müssen?

Wir könnten zusammengesetzte Daten wie ein Array oder etwas anderes verwenden ...

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => ([a, b, x]) =>
  x === 0 ? a : f ([b, a + b, x - 1]))

// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7])) 
// 0 1 1 2 3 5 8 13

Dies ist jedoch schlecht, da er den internen Status (Zähler a und b) freigibt. Es wäre schön, wenn wir einfach fibonacci (7) anrufen könnten, um die gewünschte Antwort zu erhalten.

Mit dem, was wir über Curried-Funktionen (Sequenzen unärer (1-Parameter-) Funktionen) wissen, können wir unser Ziel leicht erreichen, ohne unsere Definition von Y ändern zu müssen oder auf zusammengesetzte Daten oder erweiterte Sprachfunktionen zu setzen.

Sehen Sie sich die Definition von fibonacci genau unten an. Wir wenden sofort 0 und 1 an, die an a und b gebunden sind. Nun wartet Fibonacci einfach auf das letzte Argument, das an x gebunden wird. Wenn wir wiederkehren, müssen wir f (a) (b) (x) (nicht f (a,b,x)) aufrufen, da unsere Funktion in der aktuellen Form ist.

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => a => b => x =>
  x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)

console.log (fibonacci (7)) 
// 0 1 1 2 3 5 8 13


Diese Art von Muster kann nützlich sein, um alle Arten von Funktionen zu definieren. Im Folgenden sehen Sie zwei weitere Funktionen, die mit dem Y-Kombinator (range und reduce) und einer Ableitung von reduce, map definiert werden.

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const range = Y (f => acc => min => max =>
  min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])

const reduce = Y (f => g => y => ([x,...xs]) =>
  x === undefined ? y : f (g) (g (y) (x)) (xs))
  
const map = f =>
  reduce (ys => x => [...ys, f (x)]) ([])
  
const add = x => y => x + y

const sq = x => x * x

console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]

console.log (reduce (add) (0) ([1,2,3,4]))
// 10

console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]


ALLES IST ANONYMOS OMG

Da wir hier mit reinen Funktionen arbeiten, können wir deren Definition durch eine beliebige benannte Funktion ersetzen. Beobachten Sie, was passiert, wenn wir Fibonacci nehmen und benannte Funktionen mit ihren Ausdrücken ersetzen

/* const U = f => f (f)
 *
 * const Y = U (h => f => f (x => U (h) (f) (x)))
 *
 * const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
 *
 */

/*
 * given fibonacci (7)
 *
 * replace fibonacci with its definition
 * Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 *
 * replace Y with its definition
 * U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
 * replace U with its definition
 * (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 */

let result =
  (f => f (f)) (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
  
console.log (result) // 13

Und da haben Sie es - fibonacci (7) - rekursiv mit nichts als anonymen Funktionen berechnet

12
user633183
(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();
11
fcalderan

Sie könnten so etwas tun:

(foo = function() { foo(); })()

oder in deinem Fall:

(recur = function(data){
    data = data+1;
    var nothing = function() {
        if (data > 100) return; // put recursion limit
        recur(data);
    }
    nothing();
})(/* put data init value here */ 0);
6
ArtBIT

Warum übergeben Sie die Funktion nicht an die Funktion selbst?

    var functionCaller = function(thisCaller, data) {
        data = data + 1;
        var nothing = function() {
            thisCaller(thisCaller, data);
        };
        nothing();
    };
    functionCaller(functionCaller, data);
3

Wenn Sie eine anonyme Funktion wie diese deklarieren:

(function () {
    // Pass
}());

Es gilt als Funktionsausdruck und hat einen optionalen Namen (den Sie verwenden können, um ihn aus sich selbst heraus aufzurufen. Da es sich jedoch um einen Funktionsausdruck (und nicht um eine Anweisung) handelt, bleibt er anonym (hat jedoch einen Namen, den Sie aufrufen können) Diese Funktion kann sich selbst aufrufen:

(function foo () {
    foo();
}());
foo //-> undefined
3
xj9

In bestimmten Situationen müssen Sie sich auf anonyme Funktionen verlassen. Gegeben ist eine rekursive map-Funktion:

const map = f => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : map (f) ([...acc, f(head)]) (tail);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) ([0]) (xs)); // [0] modifies the structure of the array

Bitte beachten Sie, dass map die Struktur des Arrays nicht verändern darf. Der Akkumulator acc muss also nicht freigelegt werden. Wir können map in eine andere Funktion einbinden:

const map = f => xs => {
  let next = acc => ([head, ...tail]) => head === undefined
   ? acc
   : map ([...acc, f(head)]) (tail);

  return next([])(xs);
}

Diese Lösung ist jedoch recht ausführlich. Verwenden wir den unterschätzten U-Kombinator:

const U = f => f(f);

const map = f => U(h => acc => ([head, ...tail]) => head === undefined 
 ? acc
 : h(h)([...acc, f(head)])(tail))([]);

const sqr = x => x * x;
const xs = [1,2,3,4,5];

console.log(map(sqr) (xs));

Kurz, nicht wahr? U ist absolut simpel, hat aber den Nachteil, dass der rekursive Aufruf etwas verschleiert wird: sum(...) wird zu h(h)(...) - das ist alles.

3
user6445533

Ich bin nicht sicher, ob die Antwort noch erforderlich ist. Dies kann jedoch auch mit Hilfe von Delegaten erfolgen, die mit function.bind erstellt wurden:

    var x = ((function () {
        return this.bind(this, arguments[0])();
    }).bind(function (n) {
        if (n != 1) {
            return n * this.bind(this, (n - 1))();
        }
        else {
            return 1;
        }
    }))(5);

    console.log(x);

Dies beinhaltet keine benannten Funktionen oder Argumente.callee.

2
Nitij

Wie bobince schrieb, benennen Sie einfach Ihre Funktion.

Aber ich vermute, Sie möchten auch einen Anfangswert übergeben und Ihre Funktion irgendwann beenden!

var initialValue = ...

(function recurse(data){
    data++;
    var nothing = function() {
        recurse(data);
    }
    if ( ... stop condition ... )
        { ... display result, etc. ... }
    else
        nothing();
}(initialValue));

Arbeitendes jsFiddle-Beispiel (verwendet Daten + = Daten zum Spaß)


1
Peter Ajtai

ich brauchte (oder wollte) eine anonyme Ein-Linien-Funktion, um ein Objekt aufzubauen, das eine Zeichenfolge aufbaut, und behandelte es wie folgt:

var cmTitle = 'Root' + (function cmCatRecurse(cmCat){return (cmCat == root) ? '' : cmCatRecurse(cmCat.parent) + ' : ' + cmCat.getDisplayName();})(cmCurrentCat);

was eine Zeichenfolge wie 'Root: foo: bar: baz: ...' erzeugt.

1
radio_babylon

Mit ES2015 können wir ein wenig mit der Syntax herumspielen und die Standardparameter und Thunks missbrauchen. Letztere sind nur Funktionen ohne Argumente:

const applyT = thunk => thunk();

const fib = n => applyT(
  (f = (x, y, n) => n === 0 ? x : f(y, x + y, n - 1)) => f(0, 1, n)
);

console.log(fib(10)); // 55

// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...

Bitte beachten Sie, dass f ein Parameter mit der anonymen Funktion (x, y, n) => n === 0 ? x : f(y, x + y, n - 1) als Standardwert ist. Wenn f von applyT aufgerufen wird, muss dieser Aufruf ohne Argumente erfolgen, damit der Standardwert verwendet wird. Der Standardwert ist eine Funktion und daher ist f eine benannte Funktion, die sich selbst rekursiv aufrufen kann.

1
user6445533

Eine andere Antwort, die keine benannte Funktion oder Argumente.callee beinhaltet

var sum = (function(foo,n){
  return n + foo(foo,n-1);
})(function(foo,n){
     if(n>1){
         return n + foo(foo,n-1)
     }else{
         return n;
     }
},5); //function takes two argument one is function and another is 5

console.log(sum) //output : 15
0
jforjs

Dies ist eine Überarbeitung der Antwort von jforjs mit verschiedenen Namen und einem leicht geänderten Eintrag. 

// function takes two argument: first is recursive function and second is input
var sum = (function(capturedRecurser,n){
  return capturedRecurser(capturedRecurser, n);
})(function(thisFunction,n){
     if(n>1){
         return n + thisFunction(thisFunction,n-1)
     }else{
         return n;
     }
},5); 

console.log(sum) //output : 15

Die erste Rekursion musste nicht abgewickelt werden. Die Funktion, die sich als Referenz empfängt, erinnert an den ursprünglichen Schlupf von OOP.

0
englebart

Noch eine weitere Y-Kombinator-Lösung mit rosetta-code link (Ich denke, jemand hat den Link irgendwo auf stackOverflow erwähnt). 

Pfeile sind für anonyme Funktionen für mich besser lesbar:

var Y = f => (x => x(x))(y => f(x => y(y)(x)));
0
myfirstAnswer

Dies ist eine Version der Antwort von @ zem mit Pfeilfunktionen.

Sie können den Kombinationsfeld U oder Y verwenden. Y-Kombinator ist am einfachsten zu verwenden.

U combinator, hier müssen Sie die Funktion weitergeben: const U = f => f(f) U(selfFn => arg => selfFn(selfFn)('to infinity and beyond'))

Y combinator, damit müssen Sie nicht immer die Funktion übergeben: const Y = gen => U(f => gen((...args) => f(f)(...args))) Y(selfFn => arg => selfFn('to infinity and beyond'))

0
Ricardo Freitas