Definieren Sie eine Funktion innerhalb einer anderen Funktion in JavaScript

82
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

Ich habe eine Funktion, deren Struktur der obigen ähnlich ist. Ich möchte Aufgabe 3 in eine Funktion abstrahieren bar(), aber ich möchte den Zugriff auf diese Funktion nur im Rahmen von beschränken foo(a).

Ist es richtig, auf das Folgende zu wechseln, um das zu erreichen, was ich will?

function foo(a) {
    function bar() {
        // Perform task 3
    }

    if (/* Some condition */) {
        // Perform task 1
        bar();
    }
    else {
        // Perform task 2
        bar();
    }
}

Wenn das oben Gesagte korrekt ist, wird bar()es jedes Mal neu definiert, wenn es foo(a)aufgerufen wird? (Ich mache mir hier Sorgen um die Verschwendung von CPU-Ressourcen.)

Tamakisquare
quelle
1
Testen Sie, ob es sich lohnt: jsperf.com Ich kann mir vorstellen, dass es von task3 abhängt.
TomByrer
1
@ TomByer - +1 für den Vorschlag des Tools
Tamakisquare

Antworten:

121

Ja, was du da hast, ist richtig. Einige Notizen:

  • barwird bei jedem Aufruf der Funktion erstellt foo, aber:
    • In modernen Browsern ist dies ein sehr schneller Prozess. (Einige Engines kompilieren den Code möglicherweise nur einmal und verwenden diesen Code dann jedes Mal in einem anderen Kontext. Die V8-Engine von Google [in Chrome und anderswo] erledigt dies in den meisten Fällen.)
    • Und je nachdem, was barfunktioniert, können einige Engines feststellen, dass sie es "inline" können, wodurch der Funktionsaufruf vollständig entfällt. V8 macht das und ich bin sicher, dass es nicht der einzige Motor ist, der das macht. Natürlich können sie dies nur tun, wenn dies das Verhalten des Codes nicht ändert.
  • Die Auswirkungen auf die Leistung, bardie jedes Mal erstellt wurden, variieren stark zwischen den JavaScript-Engines. Wenn bares trivial ist, variiert es von nicht nachweisbar bis ziemlich klein. Wenn Sie nicht footausende Male hintereinander anrufen (z. B. von einem mousemoveHandler), würde ich mir darüber keine Sorgen machen. Selbst wenn Sie es sind, würde ich mir nur Sorgen machen, wenn ich ein Problem bei langsameren Motoren sehen würde. Hier ist ein Testfall mit DOM-Operationen , der darauf hindeutet, dass es eine Auswirkung gibt, aber eine triviale (wahrscheinlich vom DOM-Material verwaschen). Hier ist ein Testfall, der eine reine Berechnung durchführt, die eine viel höhere Auswirkung zeigt, aber ehrlich gesagt sprechen wir von einem Unterschied von Mikrosekunden, weil sogar ein Anstieg von 92% auf etwas, das Mikro benötigtSekunden sind immer noch sehr, sehr schnell. Bis / bis Sie einen realen Einfluss gesehen haben, ist es kein Grund zur Sorge.
  • barist nur innerhalb der Funktion zugänglich und hat Zugriff auf alle Variablen und Argumente für diesen Aufruf der Funktion. Dies macht dies zu einem sehr praktischen Muster.
  • Beachten Sie, dass , weil Sie eine Funktion benutzt habe Erklärung , es spielt keine Rolle , wo Sie die Erklärung setzen (oben, unten oder in der Mitte - so lange , wie es auf der obersten Ebene der Funktion ist, nicht in einer Strömungssteueranweisung, die ist , ein Syntaxfehler), der definiert wird, bevor die erste Zeile des schrittweisen Codes ausgeführt wird.
TJ Crowder
quelle
Danke für deine Antwort. Wollen Sie damit sagen, dass dies ein vernachlässigbarer Leistungseinbruch ist? ( barfoo
vorausgesetzt
2
@ahmoo: Bei JavaScript-Leistung lautet die Antwort fast immer: Es kommt darauf an. :-) Es hängt davon ab, welche Engine es laufen lässt und wie oft Sie anrufen werden foo. Wenn Sie nicht footausende Male hintereinander anrufen (zum Beispiel nicht in einem mousemoveHandler), würde ich mir darüber überhaupt keine Sorgen machen. Beachten Sie auch, dass einige Engines (z. B. V8) den Code ohnehin einbinden und den Funktionsaufruf vollständig eliminieren, vorausgesetzt, dies ändert nichts an dem, was extern erkannt werden kann.
TJ Crowder
@TJCrowder: Kannst du Robrichs Antwort kommentieren? Verhindert diese Lösung die Wiederherstellung bar()bei jedem Anruf? Würde es auch foo.prototype.barhelfen, die Funktion zu definieren?
rkw
4
@rkw: Das einmalige Erstellen der Funktion, wie es Robrichs Antwort tut, ist eine nützliche Methode, um die Kosten für das Erstellen bei jedem Aufruf zu vermeiden. Sie verlieren die Tatsache, dass Sie barZugriff auf die Variablen und Argumente für den Aufruf haben foo(alles, was Sie möchten, müssen Sie übergeben), was die Dinge etwas komplizieren kann, aber in einer leistungskritischen Situation, in der Sie dies tun Wenn Sie ein tatsächliches Problem gesehen haben, können Sie es so umgestalten, um zu sehen, ob es das Problem löst. Nein, die Verwendung foo.prototypewürde nicht wirklich helfen (zum einen barwäre sie nicht mehr privat).
TJ Crowder
@ahmoo: Testfall hinzugefügt. Interessanterweise erhalte ich ein anderes Ergebnis als Guffas Testfall. Ich denke, seine Funktion ist möglicherweise etwas zu einfach. Aber ich denke immer noch nicht, dass Leistung ein Problem sein muss.
TJ Crowder
15

Dafür sind Verschlüsse da.

var foo = (function () {
  function bar() {
    // perform task 3
  };

  function innerfoo (a) { 
    if (/* some cond */ ) {
      // perform task 1
      bar();
    }
    else {
      // perform task 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo (ein Verschluss) enthält einen Verweis auf bar und nur ein Verweis auf innerfoo wird von einer anonymen Funktion zurückgegeben, die nur einmal aufgerufen wird, um den Verschluss zu erstellen.

Die Bar ist auf diese Weise von außen nicht zugänglich.

Michiel Borkent
quelle
1
Interessant. Ich bin nur begrenzt mit Javascript vertraut, daher ist das Schließen für mich etwas Neues. Sie haben jedoch einen Ausgangspunkt für das Studium der Schließung markiert. Vielen Dank.
Tamakisquare
Wie oft verwenden Sie Closure, um mit Variablen- / Funktionsbereichen umzugehen? Wenn Sie beispielsweise 2 Funktionen haben, die auf dieselben 3 Variablen zugreifen müssen, würden Sie die 3 Variablen zusammen mit den 2 Funktionen in einem Abschluss deklarieren und dann die 2 Funktionen zurückgeben?
DoubleOrt
8
var foo = (function () {
    var bar = function () {
        // perform task 3
    }
    return function (a) {

        if (/*some condition*/) {
            // perform task 1
            bar();
        }
        else {
            // perform task 2
            bar();
        }
    };
}());

Der Abschluss behält den Umfang des bar()enthaltenen Bereichs bei und gibt die neue Funktion von den selbstausführenden anonymen Funktionssätzen zurück, auf die ein sichtbarerer Bereich gesetzt ist foo(). Die anonyme selbstausführende Funktion wird genau einmal ausgeführt, es gibt also nur eine bar()Instanz, und jede Ausführung von foo()wird sie verwenden.

Robrich
quelle
Interessant. Dann muss ich nachsehen. Vielen Dank.
Tamakisquare
Wie oft verwenden Sie Closure, um mit Variablen- / Funktionsbereichen umzugehen? Wenn Sie beispielsweise zwei Funktionen haben, die auf dieselben drei Variablen zugreifen müssen, würden Sie die drei Variablen zusammen mit den beiden Funktionen in einem Abschluss deklarieren und dann die beiden Funktionen zurückgeben?
DoubleOrt
Wie oft benutze ich Verschlüsse? Die ganze Zeit. Wenn ich 3 Variablen hätte, die von 2 Funktionen benötigt würden: 1. Übergeben Sie (am besten) die 3 Variablen an beide Funktionen - die Funktionen können einmal und nur einmal definiert werden. 2. (gut) Erstellen Sie die 2 Funktionen, bei denen die Variablen außerhalb des Bereichs von beiden liegen. Dies ist eine Schließung. (Grundsätzlich die Antwort hier.) Leider werden die Funktionen für jede neue Verwendung der Variablen neu definiert. 3. (schlecht) benutze keine Funktionen, habe nur eine große lange Methode, die beide Dinge macht.
Robrich
5

Ja, das funktioniert gut.

Die innere Funktion wird nicht jedes Mal neu erstellt, wenn Sie die äußere Funktion aufrufen, sondern neu zugewiesen.

Wenn Sie diesen Code testen:

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

es wird zeigen 1, 2, 1, 2, nicht 1, 2, 2, 2.

Guffa
quelle
Danke für deine Antwort. Sollte die Neuzuweisung demo()jedes Mal test()als Leistungsproblem bezeichnet werden? Kommt es auf die Komplexität von an demo()?
Tamakisquare
1
Ich habe einen Leistungstest durchgeführt: jsperf.com/inner-function-vs-global-function Die Schlussfolgerung ist, dass es sich im Allgemeinen nicht um ein Leistungsproblem handelt (da die Ausführung von Code, den Sie in die Funktionen eingeben, viel länger dauert als das Erstellen der Funktion selbst), aber wenn Sie diesen zusätzlichen Leistungsvorteil benötigen würden, müssten Sie unterschiedlichen Code für verschiedene Browser schreiben.
Guffa
Vielen Dank, dass Sie sich die Zeit genommen haben, den Test zu erstellen und Ihre Leistungspunkte mitzuteilen. Sehr geschätzt.
Tamakisquare
Sie haben mit großem Vertrauen gesagt, dass "die innere Funktion nicht jedes Mal neu erstellt wird". Nach der Spezifikation ist es; Ob ein Motor optimiert, hängt vom Motor ab. (Ich gehe davon aus, dass die meisten dies tun werden.) Ich bin fasziniert zu sehen, dass Ihr und mein Testfall so unterschiedliche Ergebnisse haben: jsperf.com/cost-of-creating-inner-function Nicht, dass ich denke, dass Leistung ein Problem ist.
TJ Crowder
@TJCrowder: Nun ja, es ist ein Implementierungsdetail, aber da moderne Javascript-Engines den Code kompilieren, wird die Funktion nicht jedes Mal neu kompiliert, wenn sie zugewiesen wird. Der Grund, warum sich die Ergebnisse der Leistungstests unterscheiden, liegt darin, dass sie verschiedene Dinge testen. Mein Test vergleicht globale Funktionen mit lokalen Funktionen, während Ihr Test eine lokale Funktion mit Inline-Code vergleicht. Das Inlinen des Codes ist natürlich schneller als das Aufrufen der Funktion. Dies ist eine gängige Optimierungstechnik.
Guffa
0

Ich habe ein jsperf erstellt, um verschachtelte vs. nicht verschachtelte und Funktionsausdrücke vs. Funktionsdeklarationen zu testen, und ich war überrascht zu sehen, dass die verschachtelten Testfälle 20x schneller als nicht verschachtelte ausgeführt wurden. (Ich habe entweder das Gegenteil oder vernachlässigbare Unterschiede erwartet).

https://jsperf.com/nested-functions-vs-not-nested-2/1

Dies ist auf Chrome 76, macOS.

Michael Liquori
quelle