MB: Wofür verwendet man welche funktionstypen???

moin,

ich habe 4 Funktionstypen und die Unterschiede kennen gelernt.

  • Function Statement (FnS)
    wird bei der Creation Phase (CP) - welcher lexikalisch den Code strukturiert - der Funktions-Körper reingeladen aber noch nicht ausgeführt.
  • Function Execution (FnE)
    wird zur Execution Phase (EP) zugewiesen und ersetzt undefined nach der (CP)
  • Immediately Invoked Function Execution (IIFE)
    wird sofort ausgeführt nach dem erreichen des Cursors in der CP
  • Arrow-Function (ArrFn)
    sind Shorthand FnE ohne bezug zu this-Objekt

Alle Funktionen (Fn) sind Objekte! Das ist mein aktueller Wissenstand.

Wofür verwende ich welche Fn am sinnvollsten??? Meine Forschung auf YouTube, StackOverflow und generell Google war wenig ertragreich 😕.

  • Wann sollte man FnE anstatt FnS nutzen, wann nicht und umgekehrt??
  • Wozu sollte man IIFE nutzen, wenn man nicht client-seitig ein Framework bekanntmacht um mit ihm zu arbeiten???

Ich hab nur erfahren das ArrFn sinnvoll für Callbacks und Closures sind und FnE ersetzt da sie Shorthand sind und keinen bezug zu dem this-Objekt haben im gegensatz zu FnE.

lgmb

--
Sprachstörung

akzeptierte Antworten

  1. Hallo MB,

    du meinst wohl function expression, nicht function execution

    Was die beste Art ist, eine Funktion zu definieren, kann man – wenn überhaupt – nur im Einzelfall sagen. Ich finde es auch schwierig, da allgemeine Regeln zu formulieren. Daher nur ein paar Anmerkungen …

    Grundsätzlich wäre meine Empfehlung, Pfeilfunktionen (arrow functions) zu verwenden, wenn man keine Bindung für this benötigt. Im Gegensatz zu gewöhnlichen Funktionsobjekten haben Pfeilfunktionen auch kein prototype-Objekt und es steht kein arguments-Objekt zur Verfügung, das ohnehin nicht mehr verwendet werden sollte.

    Ich gehe davon aus, dass dieser kleine Overhead von modernen Ausführungsumgebungen wegoptimiert wird, aber trotzdem kann man dann schon an der Definition erkennen, wie bzw. wofür die Funktion nicht verwendet werden wird.

    Persönlich finde ich Pfeilfunktionen auch syntaktisch ansprechender, aber das ist wohl Geschmackssache. 😉

    const identity = x => x
    

    Eine solche Deklaration mit const hat jedenfalls den Vorteil, dass der Name später nicht mehr unbeabsichtigt überschrieben werden kann.

    function identity(x) {
      return x
    }
    

    Wenn man es hingegen so als function statement schreibt, ist (so wie bei Variablen die mit var deklariert werden) eine spätere Redeklaration möglich. Das will man aber in aller Regel nicht, denn das macht den Code intransparent. Die erste Variante garantiert dir also, dass im entsprechende Scope die Bindung von Name und Funktion konstant bleibt.

    Dazu kommt, dass bei einer gewöhnlichen Funktionsdeklaration die Deklaration gehoisted wird, das heißt, sie wird implizit an den Anfang des jeweiligen Scopes gezogen. Das heißt, man kann die Funktion aufrufen, bevor man sie im Code deklariert hat. Das ist allerdings keine gute Praxis, da es den Code ebenfalls schwer lesbar macht. Solche Konstrukte kann man durch die Verwendung eines Funktionsausdrucks und die Deklaration mit const (oder let) von vorneherein ausschließen.

    Das würde dann allerdings auch für den Fall gelten, dass man tatsächlich eine gewöhnliche Funktion mit this-Bindung unabhängig Deklarieren möchte.

    const show = function() {
        console.log(this.data.toString())
    }
    
    show.call(object)
    

    Also zusammengefasst: Pfeilfunktion wenn möglich, gewöhnliche Funktion wenn nötig, und lieber function expression mit konstanter Variablenbindung als function statement.

    Was man bei der Verwendung von Pfeilfunktionen häufig sieht, ist allerdings eine Tendenz dazu, zu viele anonyme Funktionen zu verwenden, wie zum Beispiel bei den von dir angesprochenen Callbacks. Das macht Code manchmal unnötig schwer lesbar.

    const ys = xs.map(x => x**2)
    

    versus

    const square = x => x**2
    
    const ys = xs.map(square)
    

    Wenn man eine Funktion sinnvoll bennen kann, sollte man es in aller Regel tun. Das macht den Code besser verständlich. Anonyme Funktionsausdrücke würde ich nur verwenden, wenn die Funktion eher nicht noch einmal an anderer Stelle verwendet wird und eine Benennung nicht groß zum Verständnis des Codes beitragen würde.

    Die von dir angesprochenen IIFEs (Immediately Invoked Function Expressions) haben heute eigentlich kaum noch sinnvolle Anwendungsfälle. Das wurde vor der Einführung von Modulen und Blockscope-Variablen (const und let) zur Kapselung verwendet, aber für diesen Zweck braucht man das heute nicht mehr.

    {
      // Block scope
      const n = 42
    }
    
    console.log(n) // ReferenceError
    

    Ein weiterer Anwendungsfall wäre ein async-IIFE in Browsern, die kein globales await unterstützen. Aber inwieweit das noch relevant ist, weiß ich nicht. Ich kann mich jedenfalls nicht daran erinnern, wann ich dieses Konstrukt tatsächlich das letzte Mal wirklich gebraucht habe.

    Viele Grüße,

    Matthias

    1. moin,

      du meinst wohl function expression, nicht function execution

      Oh wie peinlich, sorry 😓

      Eine solche Deklaration mit const hat jedenfalls den Vorteil, dass der Name später nicht mehr unbeabsichtigt überschrieben werden kann ,…].

      Stimmt, das ist ein guter Einwand ☝️.

      Dazu kommt, dass bei einer gewöhnlichen Funktionsdeklaration die Deklaration gehoisted wird, das heißt, sie wird implizit an den Anfang des jeweiligen Scopes gezogen. Das heißt, man kann die Funktion aufrufen, bevor man sie im Code deklariert hat. Das ist allerdings keine gute Praxis, da es den Code ebenfalls schwer lesbar macht. Solche Konstrukte kann man durch die Verwendung eines Funktionsausdrucks und die Deklaration mit const (oder let) von vorneherein ausschließen.

      Sry, mir ist nicht ganz klar was du mit deiner obrigen Ausführung meist. Kanst du mir n Beispiel anführen mit mir dem bekannten hoisting?

      Also zusammengefasst: Pfeilfunktion wenn möglich, gewöhnliche Funktion wenn nötig, und lieber function expression mit konstanter Variablenbindung als function statement.

      sehr einleuchtend. Vielen Dank 😀

      Was man bei der Verwendung von Pfeilfunktionen häufig sieht, ist allerdings eine Tendenz dazu, zu viele anonyme Funktionen zu verwenden, wie zum Beispiel bei den von dir angesprochenen Callbacks. Das macht Code manchmal unnötig schwer lesbar.

      Ich schätze mal, das kann man über JSDoc Callback realsieren, wenn der code mit verschachtelte Closures unleserlich wird (nennt sich das nicht Currying 🤔?), oder macht man das nicht? Der Code mit Kommentaren jedefalls sähe dann für meine Begriffe sehr aufgebläht aus, aber für die Beschreibung der Verwendung der Aktion finde ich es sehr praktikabel.

      Die von dir angesprochenen IIFEs (Immediately Invoked Function Expressions) haben heute eigentlich kaum noch sinnvolle Anwendungsfälle. […]

      Das wusste ich nicht. Danke für den Hinweis 😉.

      Schönen Sonntagabend wunsche ich.

      lgmb

      --
      Sprachstörung
      1. Hallo MB,

        das ist jetzt Herumreiten auf Kleinigkeiten, aber ich glaube, genau die interessieren Dich. Tom - bitte weggucken. Buzzwordsturm im Anzug!

        Hoisting kennst Du - als das "Hochziehen" einer Deklaration an den Anfang des Bereichs, in dem sie stattfindet.

        Ohne Hoisting wäre

        let x = foo(7);
        function foo(a)
        {
           return a+1;
        }
        

        ein Fehler, weil foo im Moment des Aufrufs noch nicht deklariert ist. Sprachen wie C oder C++ würden Dir diesen Code abweisen. Was daran liegt, dass C für eine Welt geschaffen wurde, in der ein Two-Pass Compiler unanständiger Luxus war. Aber die "Declare before Use" Forderung hat sich dort gehalten.

        "Deklarieren" bedeutet dabei, dem Compiler mitzuteilen, dass dieser Name einer ist, den ich verwenden will, so dass der Compiler weiß, dass foo kein Tippfehler ist. Je nach Sprache teile ich dem Compiler auch gleich mit, wofür ich den Namen verwenden will - was man in JavaScript aber nicht tut.

        Das Zuordnen eines Wertes zu diesem Namen ist dann das "Definieren". Die Besonderheit des function Statements ist, dass es eine Funktion nicht nur deklariert, sondern auch gleich definiert. Bei Variablen wird die Deklaration zwar gehoistet, aber die Definition erfolgt an der Stelle, wo das var, const oder let Statement steht.

        let und const werden gehoistet? Eigentlich nicht, und eigentlich doch. Gleich mehr dazu.

        Und - Compiler? Ja, sicher. JavaScript übersetzt den Sourcecode erstmal komplett, bevor es ihn ausführt, in einen Bytecode (die "Creation Phase" eben). Ältere JS Engines haben den Bytecode dann interpretiert. Neuere Engines wie Google V8 oder Firefox Spidermonkey übersetzen den Bytecode einer Funktion vor ihrer ersten Ausführung erstmal in Maschinencode. Mozilla erzählt ganz stolz, dass sie das besonders geschickt machen: erstmal "grob" übersetzen, und wenn sie feststellen, dass eine Funktion öfter aufgerufen wird, läuft ein optimierender Compiler im Hintergrund nochmal drüber.

        Das Hoisting hat so seine Merkwürdigkeiten. Wenn ich einen Namen doppelt benutzer, für var und function, meckert JS nicht. Aber die Funktion wird ignoriert. Egal, wie herum es aufgeschrieben ist. Sudhakar Pilavadi schreibt zwar, dass bei einem Namenskonflikt Funktionen Vorrang vor Variablen hätten, aber mein Chrome tut das Gegenteil.

        var x = 7;
        function x() { };
        console.log(x);      // 7
        

        Ich sehe die 7, egal ob das var vor oder hinter dem function statement steht.

        Wie gesagt: das function Statement deklariert UND definiert, deswegen kann im Eingangsbeispiel die foo-Funktion schon vor dem function Statement verwendet werden.

        In einem var Statement wird dagegen nur der Name bekannt gemacht. Die Zuweisung des Wertes erfolgt an der Stelle, wo das var notiert ist:

        console.log(foo(2));
        var foo = function(a) {
           return a + 1;
        };
        

        Der Variablenname foo ist in Zeile 1 bereits bekannt, aber die function expression ist noch nicht zugewiesen. Deswegene enthält foo in der Zeile 1 noch den Wert undefined und es gibt eine Fehlermeldung.

        Bei const und let ist es strenger. Auch hier werden die Namen im ganzen Scope bekannt gemacht, aber eine Wiederverwendung dieser Namen ist verboten. Ein Name, der mit const oder let deklariert wurde, kann nicht mehr etwas anderes verwendet werden, und umgekehrt kann mit const oder let kein anderweit deklarierter Name redeklariert werden.

        Hinzu kommt, dass ein mit const oder let deklarierte Name erst "angefasst" werden darf, wenn das const/let Statement durchlaufen wurde. JavaScript kennt den Namen durchaus schon vorher.

        console.log("huhu");
        console.log(foo);
        

        gibt "huhu" aus und meckert dann mit einem ReferenceError, dass "foo" undefiniert sei. Hier dagegen

        console.log("huhu");
        console.log(foo);
        const foo = 12;
        

        kommt nach dem "huhu" ebenfalls ein ReferenceError, aber mit dem Text, dass foo vor seiner Initialisierung verwendet würde.

        Wenn Du also sowas baust:

        foo(12);
        
        const foo = function(x) {
           return x+1;
        }
        

        dann zwingst Du Dich dazu, deine Funktionen hinzuschreiben, bevor Du sie benutzt.

        Und an dieser Stelle bin ich durchaus anderer Meinung als Matthias. Es ist nämlich aus meiner Sicht durchaus sinnvoll, Code "top down" aufzuschreiben. Wenn ich ein Programm entwerfe, und wenn ich ein fremdes Programm verstehen will, gehe ich top-down vor. Ich überlege mir (oder schaue mir an), was das Programm auf oberster Ebene tut, und wenn die Funktionen, die dort aufgerufen werden, gute Namen tragen, muss ich mir ihre Implementierung nicht anschauen, um zu wissen, was auf oberster Ebene passiert. Wenn ich Dinge genauer wissen muss, steige ich in die zweite Ebene ein, und so weiter.

        Code, der zunächst einmal eine Geröllhalde aus kleinteiligen Funktionen daherkübelt und erst am Ende "zur Sache" kommt, muss ich von unten nach oben lesen, um ihn zu verstehen. Und das finde ich viel unübersichtlicher. Bzw. wenn ich selbst programmiere und erst einmal einen Haufen Kroppzeug als unterste Ebene zusammenprogrammiere, schreibe ich vermutlich eine Menge Code auf Vorrat, den ich nachher eventuell gar nicht brauche.

        Beim top-down Ansatz kann ich manche Funktionen zumächst als Stub programmieren und Dummy-Werte zurückliefern, und die oberste Ebene sauber gestalten, bevor ich in die Einzelheiten gehe. Aber das mag eine Frage persönlichen Stils sein.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. moin,

          das ist jetzt Herumreiten auf Kleinigkeiten, aber ich glaube, genau die interessieren Dich. Tom - bitte weggucken. Buzzwordsturm im Anzug!

          😂

          Hoisting kennst Du - als das "Hochziehen" einer Deklaration an den Anfang des Bereichs, in dem sie stattfindet.

          jepp, war mir klar, wie schon erwähnt

          ein Fehler, weil foo im Moment des Aufrufs noch nicht deklariert ist. […]
          […] Das Hoisting hat so seine Merkwürdigkeiten. […]

          War mir bekannt auch durch einen Tutor 👍.

          […], aber mein Chrome tut das Gegenteil.

          Sach bloß! Danke für den Hinweis 😀👍

          Bei const und let ist es strenger. Auch hier werden die Namen im ganzen Scope bekannt gemacht, aber eine Wiederverwendung dieser Namen ist verboten. Ein Name, der mit const oder let deklariert wurde, kann nicht mehr etwas anderes verwendet werden, und umgekehrt kann mit const oder let kein anderweit deklarierter Name redeklariert werden.

          Mit letund const war mir das klar aber nicht im detail. Danke Dafür.

          Hinzu kommt, […]

          Das war mir klar.

          Und an dieser Stelle bin ich durchaus anderer Meinung als Matthias. Es ist nämlich aus meiner Sicht durchaus sinnvoll, Code "top down" aufzuschreiben.[…]

          mache ich maßgeblich auch allerding im bescheidenen stiel 😏.

          Code, der zunächst einmal eine Geröllhalde aus kleinteiligen Funktionen daherkübelt und erst am Ende "zur Sache" kommt, […]
          […]. Aber das mag eine Frage persönlichen Stils sein.

          dito. Danke für deine ausguebe Eröäuterung. Hilft mir 😀👍

          lgmb

          --
          Sprachstörung