wake-up-neo.com

Aufruf einer Javascript-Funktion rekursiv

Ich kann eine rekursive Funktion in einer Variablen wie folgt erstellen:

/* Count down to 0 recursively.
 */
var functionHolder = function (counter) {
    output(counter);
    if (counter > 0) {
        functionHolder(counter-1);
    }
}

Damit würde functionHolder(3);3210 ausgeben. Angenommen, ich habe Folgendes getan:

var copyFunction = functionHolder;

copyFunction(3); würde 3210 wie oben ausgeben. Wenn ich dann functionHolder wie folgt geändert habe:

functionHolder = function(whatever) {
    output("Stop counting!");

Dann würde functionHolder(3);Stop counting! geben, wie erwartet.

copyFunction(3); gibt nun 3Stop counting! aus, da es sich auf functionHolder bezieht, nicht auf die Funktion (auf die es selbst verweist). Dies könnte unter bestimmten Umständen wünschenswert sein, aber gibt es eine Möglichkeit, die Funktion so zu schreiben, dass sie sich selbst aufruft und nicht die Variable, die sie enthält?

Das heißt, ist es möglich, nur die Zeile functionHolder(counter-1); zu ändern, so dass beim Aufrufen von copyFunction(3); nach all diesen Schritten immer noch 3210 angezeigt wird? Ich habe this(counter-1); versucht, aber das gibt mir den Fehler this is not a function.

83
Samthere

Verwenden benannter Funktionsausdrücke:

Sie können einem Funktionsausdruck einen Namen geben, der tatsächlich privat ist und nur innerhalb der Funktion sichtbar ist, wenn:

var factorial = function myself (n) {
    if (n <= 1) {
        return 1;
    }
    return n * myself(n-1);
}
typeof myself === 'undefined'

Hier ist myself nur innerhalb der Funktion selbst sichtbar.

Mit diesem privaten Namen können Sie die Funktion rekursiv aufrufen.

Sehen 13. Function Definition der ECMAScript 5-Spezifikation:

Der Bezeichner in einer FunctionExpression kann aus dem FunctionBody der FunctionExpression referenziert werden, damit die Funktion sich selbst rekursiv aufrufen kann. Anders als in einer FunctionDeclaration kann jedoch nicht auf den Bezeichner in einer FunctionExpression verwiesen werden, und dies hat keine Auswirkungen auf den Bereich, der die FunctionExpression einschließt.

Bitte beachten Sie, dass sich Internet Explorer bis Version 8 nicht richtig verhält, da der Name tatsächlich in der umgebenden Variablenumgebung sichtbar ist und auf ein Duplikat der tatsächlichen Funktion verweist (siehe patrick dw 's kommentiere unten).

Verwendung von arguments.callee:

Alternativ können Sie arguments.callee um sich auf die aktuelle Funktion zu beziehen:

var factorial = function (n) {
    if (n <= 1) {
        return 1;
    }
    return n * arguments.callee(n-1);
}

Die 5. Ausgabe von ECMAScript verbietet die Verwendung von arguments.callee () in strikter Modus , jedoch:

(From MDN ): In normalem Code verweist arguments.callee auf die einschließende Funktion. Dieser Anwendungsfall ist schwach: Nennen Sie einfach die umschließende Funktion! Darüber hinaus behindert arguments.callee Optimierungen wie Inlining-Funktionen erheblich, da beim Zugriff auf arguments.callee ein Verweis auf die nicht inlinierte Funktion bereitgestellt werden muss. arguments.callee für Funktionen im strengen Modus ist eine nicht löschbare Eigenschaft, die beim Festlegen oder Abrufen ausgelöst wird.

140
Arnaud Le Blanc

Sie können auf die Funktion selbst zugreifen, indem Sie arguments.callee[MDN] :

if (counter>0) {
    arguments.callee(counter-1);
}

Dies wird jedoch im strikten Modus unterbrochen.

9
Felix Kling

Ich weiß, dass dies eine alte Frage ist, aber ich dachte, ich würde eine weitere Lösung vorstellen, die verwendet werden könnte, wenn Sie benannte Funktionsausdrücke vermeiden möchten. (Ohne zu sagen, dass Sie sie meiden sollten oder nicht, sondern nur eine andere Lösung präsentieren sollten)

  var fn = (function() {
    var innerFn = function(counter) {
      console.log(counter);

      if(counter > 0) {
        innerFn(counter-1);
      }
    };

    return innerFn;
  })();

  console.log("running fn");
  fn(3);

  var copyFn = fn;

  console.log("running copyFn");
  copyFn(3);

  fn = function() { console.log("done"); };

  console.log("fn after reassignment");
  fn(3);

  console.log("copyFn after reassignment of fn");
  copyFn(3);
5
Sandro

Sie können den Y-Kombinator verwenden: ( Wikipedia )

// ES5 syntax
var Y = function Y(a) {
  return (function (a) {
    return a(a);
  })(function (b) {
    return a(function (a) {
      return b(b)(a);
    });
  });
};

// ES6 syntax
const Y = a=>(a=>a(a))(b=>a(a=>b(b)(a)));

// If the function accepts more than one parameter:
const Y = a=>(a=>a(a))(b=>a((...a)=>b(b)(...a)));

Und Sie können es wie folgt verwenden:

// ES5
var fn = Y(function(fn) {
  return function(counter) {
    console.log(counter);
    if (counter > 0) {
      fn(counter - 1);
    }
  }
});

// ES6
const fn = Y(fn => counter => {
  console.log(counter);
  if (counter > 0) {
    fn(counter - 1);
  }
});
4
Nicolò

Hier ist ein sehr einfaches Beispiel:

var counter = 0;

function getSlug(tokens) {
    var slug = '';

    if (!!tokens.length) {
        slug = tokens.shift();
        slug = slug.toLowerCase();
        slug += getSlug(tokens);

        counter += 1;
        console.log('THE SLUG ELEMENT IS: %s, counter is: %s', slug, counter);
    }

    return slug;
}

var mySlug = getSlug(['This', 'Is', 'My', 'Slug']);
console.log('THE SLUG IS: %s', mySlug);

Beachten Sie, dass das counter in Bezug auf den Wert von slug "rückwärts" zählt. Dies liegt an der Position, an der wir diese Werte protokollieren, da die Funktion wiederkehrt vor dem Protokollieren - also schachteln wir im Wesentlichen immer tiefer in den Aufrufstapelvorher es wird protokolliert.

Sobald die Rekursion das letzte Call-Stack-Element erreicht hat, ist sie Trampoline "out" der Funktionsaufrufe, wohingegen das erste Inkrement von counter innerhalb des letzten verschachtelten Aufrufs auftritt.

Ich weiß, dass dies kein "Fix" für den Code des Fragestellers ist, aber angesichts des Titels, von dem ich dachte, ich würde generisch Rekursion veranschaulichen, um die Rekursion besser zu verstehen.

3
Cody