piet: function mit beliebig vielen Übergabeparametern

Hallo,

ich möchte eine Funktion schreiben, die beliebig viele, jedoch min. 1 Übergabeparameter bekommt. Wie sieht hier die Syntax der Funktion bzw. wie frage ich eventuell per Zähler die Anzahl der Funktionen ab. ?

z.B.

function myfirsttest (immer, beliebig_1, beliebig_2, beliebig_3, beliebig_x)

Ich steigere meine Ansprüche an meine eigenen Programme ;-)

Danke

  1. Lieber piet,

    function myfirsttest (immer, beliebig_1, beliebig_2, beliebig_3, beliebig_x)
    

    dafür notiert man überhaupt kein Argument (so lautet Dein Stichwort!), sondern ermittelt innerhalb der Funktion mittels arguments, wieviele Argumente beim Aufruf übertragen wurden.

    Liebe Grüße,

    Felix Riesterer.

    1. Hallo Felix

      function myfirsttest (immer, beliebig_1, beliebig_2, beliebig_3, beliebig_x)
      

      dafür notiert man überhaupt kein Argument (so lautet Dein Stichwort!), sondern ermittelt innerhalb der Funktion mittels arguments, wieviele Argumente beim Aufruf übertragen wurden.

      Was hier wohl verlangt ist, ist ein formaler Parameter und die Möglichkeit darüber hinaus eine unbestimmte Anzahl weiterer Argumente entgegenzunehmen, was ich in modernem JavaScript unter Verwendung der Syntax für Restparameter so schreiben würde:

      function name (param, ...rest) {
        console.log(rest.length);
        return param;
      }
      
      const value = name(16, 32, 64); // 2
      
      console.log(value); // 16
      

      Hierbei kann der formale Parameter innerhalb der Funktion direkt über seinen Bezeichner angesprochen werden und alle weiteren, gegebenenfalls beim Aufruf übergebenen Argumente werden in einem Array gespeichert, dass über den Bezeichner des Restparameters referenziert werden kann. Allerdings ist diese Syntax noch nicht flächendeckend implementiert.

      function name (param) {
        var rest = [ ].slice.call(arguments, 1);
        console.log(rest.length);
        return param;
      }
      
      var value = name(16, 32, 64); // 2
      
      console.log(value); // 16
      

      Das gleiche Ergebnis kann man aber auch erzielen, indem man nur einen Parameter notiert und dann die Arraymethode slice im Kontext von arguments aufruft. Dabei wird die Funktionsvariable arguments als erstes Argument an call übergeben. Der zweite an call übergebene Wert ist dann das erste Argument für die Methode slice, welches den Index des ersten berücksichtigten Elementes angibt.

      Wird wie hier 1 übergeben, wird das erste an die Funktion übergebene Argument für das Array mit den restlichen Argumenten nicht berücksichtigt, das heißt dieses enthält, sofern mehr als ein Argument übergeben wurde, alle Argumente außer demjenigen, mit dem der formale Paramter initialisiert wurde.

      function name (param) {
        // do something
      }
      
      console.log(name.length); // 1
      

      Das hat den Vorteil, dass man über die Abfrage der Eigenschaft length der Funktion die Anzahl der formalen Parameter ermitteln kann, zu denen angegebene Restparameter wie im ersten Beispiel oben nicht hinzugezählt werden.

      Viele Grüße,

      Orlok

      1. Lieber Orlok,

        vielen Dank für Deinen ausführlichen und erhellenden Beitrag!

        in modernem JavaScript unter Verwendung der Syntax für Restparameter so schreiben würde:

        function name (param, ...rest) {
          console.log(rest.length);
          return param;
        }
        

        Ja, das ist wohl ECMA6, das habe ich bisher schön ausgeblendet. Bin froh, dass ich JavaScript und seine Besonderheiten bis ECMA5 einigermaßen verstanden habe.

        Und ich will nicht wissen, was @Gunnar Bittersmann zu diesen drei Punkten sagt: ...rest vs …rest? LOL

        Allerdings ist diese Syntax noch nicht flächendeckend implementiert.

        Das sehe ich allerdings als schwerwiegendes Argument an.

        mit dem der formale Paramter

        Ich habe einmal eine Art sprintf für JavaScript gesucht und etwas bei StackOverflow gefunden, das sich arguments zunutze macht, aber ohne "formalen Parameter". Der hätte in diesem Beispiel keinen Sinn. Oh, eben merke ich gerade, dass es da eine nette sprintf-Implementation für das node.js-Projekt gibt, die das wirklich sehr zufrieden umzusetzen scheint - muss ich mal bei Gelegenheit ausprobieren.

        Liebe Grüße,

        Felix Riesterer.

        1. Hallo @Felix Riesterer

          Ja, das ist wohl ECMA6, das habe ich bisher schön ausgeblendet.

          Ich finde die Beschäftigung mit ECMAScript 2015 lohnt sich und ich kann es gar nicht erwarten, dass endlich alle neuen Features von den relevanten Ausführungsumgebungen unterstützt werden, zumal sich damit viele alltägliche Aufgaben deutlich eleganter lösen lassen, was eben insbesondere auch das Handling von Parametern und Argumenten betrifft. ;-)

          function name (parameter, ...rest) {
            console.log(typeof parameter);
            return rest;
          }
          
          const value = name('argument', 16, 32, 64); // string
          
          console.log(Array.isArray(value)); // true
          

          Den Restparameter hatten wir ja schon in meiner ersten Antwort und ich finde gerade im Vergleich zu der Alternative, Array.prototype.slice im Kontext von arguments aufzurufen, um formale Parameter von sonstigen Argumenten zu trennen, ist diese Syntax doch erheblich kürzer und deutlich besser lesbar.

          Und ich will nicht wissen, was @Gunnar Bittersmann zu diesen drei Punkten sagt: ...rest

          Hehe … und es kommt noch schlimmer, oder besser, denn der Operator ... hat auch noch zwei komplett verschiedene, geradezu gegensätzliche Bedeutungen, abhängig davon wo er notiert ist. Wird er etwa wie in dem Beispiel oben in der Parameterliste einer Funktion notiert, dann sammelt er als Rest Operator die beim Funktionsaufruf übergebenen Argumente ein und macht daraus Elemente eines Arrays …

          function name ( ) {
            // check if arguments is iterable
            console.log(typeof arguments[Symbol.iterator]);
            return [...arguments];
          };
          
          const value = name(2, 4, 8); // function
          
          console.log(Array.isArray(value)); // true
          

          … wird er hingegen beispielsweise in einem Arrayliteral notiert und ihm dabei ein iterierbares Objekt übergeben, dann nimmt er als Spread Operator die Elemente dieses Objektes und fügt sie in das Array ein. Also auch ohne einen Restparameter zu verwenden, kann man mit diesem Operator das Kopieren der Elemente von arguments in ein Array deutlich eleganter lösen als früher.

          const args = ['number', 128];
          
          class Name {
            constructor (property, value) {
              this[property] = value;
            }
          }
          
          const object = new Name(...args);
          
          console.log(object.number); // 128
          

          Der Operator kann aber auch bei der Übergabe von Argumenten verwendet werden, um ähnlich wie bei der Methode Function.prototype.apply Elemente eines Arrays in Argumente umzuwandeln, was besonders beim Konstruktorenaufruf von Nutzen ist, wenn dabei ein Array mit den Argumenten übergeben werden soll …

          var args = ['number', 256];
          
          function Name (property, value) {
            'use strict';
            this[property] = value;
          }
          
          var object = new (Function.prototype.bind.apply(Name, [null].concat(args)));
          
          console.log(object.number); // 256
          

          … denn hier wären sonst einige Verrenkungen nötig, da ein Aufruf mit Function.prototype.apply immer nur über [[Call]] erfolgt und nicht über [[Construct]], die Methode apply für sich genommen also nur für Funktions- und Methodenaufrufe zu gebrauchen ist. – Ich denke die Vorteile der Verwendung des Spread Operators im Vergleich zu dieser Variante liegen wohl auf der Hand. ;-)

          ##Destrukturierung

          Darüber hinaus ist auch und gerade im Zusammenhang mit Parametern die neue Syntax zur Destrukturierung von Arrays und Objekten äußerst interessant.

          const list = [512, 'element'];
          
          function name ([item, ...rest], parameter) {
            console.log(rest[0]);
            return item;
          }
          
          const value = name(list, 1024); // element
          
          console.log(value); // 512
          

          Das heißt, wenn als Parameter ein Array erwartet wird, kann man das in der Parameterliste wie in dem Beispiel oben notieren, wobei die innerhalb der eckigen Klammern angegebenen Bezeichner mit den ersten Elementen des als Argument übergebenen Arrays initialisiert werden. Dabei kann dann natürlich auch wieder der Rest Operator verwendet werden, um die verbliebenen Elemente aufzusammeln. :-)

          const array = [32, 64, 128];
          
          let [item, ...rest] = array;
          
          console.info(item); // 32
          
          console.info(Array.isArray(rest)); // true
          

          Die Destrukturierung von Arrays die als Argument übergeben wurden in der Parameterliste funktioniert also letztlich genauso wie die Destrukturierung bei der Initialisierung von Konstanten und Variablen. Das gilt aber nicht nur für Arrays, sondern auch für Objekte, was natürlich praktisch ist, da es bei einer vielzahl formaler Parameter meist von Vorteil ist, wenn an Stelle einzelner Werte ein Objekt erwartet wird, da man sich hierbei nicht die richtige Reihenfolge der Parameter merken muss.

          const object = {
            method ({message, property, value}) {
              this[property] = value;
              return message;
            }
          };
          
          const result = object.method({
            property : 'number',
            value : 256,
            message :'text'
          });
          
          console.info(result); // text
          
          console.info(object.number); // 256
          

          Die neu eingeführte Syntax zur Methodendefinition, also ohne Doppelpunkt und ohne function, gehört übrigens auch zu den Features die ich nicht mehr missen möchte. … Jedenfalls ist klar, dass man sich bei der Destrukturierung eines als Argument übergebenen Objektes diverse Zeilen Code nach dem Schema

          var property = object.property,
              value = object.value; // etc.
          

          … sparen kann. Wie gesehen, ist diese Syntax aber ebenso wie bei Arrays nicht auf Parameterlisten beschränkt, sondern kann auch verwendet werden um die Werte von Objekteigenschaften auf Konstanten oder Variablen zu kopieren, wie im nächsten Beispiel zu sehen ist.

          const object = {
            string : 'text',
            number : 16
          };
          
          let {string, number} = object;
          
          console.log(string); // text
          

          Sowohl bei der Destrukturierung von Arrays als auch bei der Destrukturierung von Objekten gilt im Übrigen, dass unabhängig davon an welcher Stelle die Operation durchgeführt wird, nicht existierende Elemente oder nicht definierte Eigenschaften den Wert undefined zurückgeben. Es wird in diesem Fall also keine Ausnahme geworfen.

          ##Default Parameter

          Ebenfalls sehr interessant im Zusammenhang mit dem Thema Parameter und Argumente ist natürlich die neue Syntax für Default Values, denn die Angabe von Standardwerten muss jetzt nicht mehr im Körper der Funktion erfolgen, sondern kann bereits bei der Deklaration der Parameter vorgenommen werden.

          function name (number = 64, object = {string : 'text'}) {
            console.log(object.string);
            return number;
          }
          
          const value = name( ); // text
          
          console.log(value); // 64
          

          Dabei wird die Zuweisung des Standardwertes prinzipiell durch undefined ausgelöst, also selbst dann, wenn dieser Wert explizit beim Aufruf übergeben wurde. Darüber hinaus können die Standardwerte auch andere, in der gleichen Liste spezifizierte Parameter referenzieren, allerdings nur, wenn diese zu dem Zeitpunkt bereits ausgewertet wurden, was immer von links nach rechts passiert.

          function name (string = 'message', other = string) {
            console.log(other);
          }
          
          name( ); // message
          

          Ebenfalls referenziert werden können natürlich Bezeichner der äußeren lexikalischen Umgebung, nicht jedoch Variablen, Konstanten und Funktionen, die im Körper der Funktion deklariert werden. Das heißt, die Parameter besitzen einen eigenen Scope, der zwischen dem Funktionskörper und der äußeren Umgebung liegt. Darüber hinaus ist allerdings auch zu beachten, dass der Ausdruck auf der rechten Seite einer solchen Deklaration grundsätzlich erst dann ausgewertet wird, wenn die Funktion aufgerufen wurde und das jeweilige Argument nicht definiert ist.

          function name (value = function (parameter) {
            console.log('invoked');
            return parameter;
          }('argument')) {
            return value;
          }
          
          name('message');
          
          const value = name( ); // invoked
          
          console.log(value); // argument
          

          Ein hier notierter Immediately-invoked Function Expression wird also ebenfalls nicht bei der Deklaration der Funktion sondern erst bei der Auswertung des Zuweisungsausdrucks ausgeführt. :-)

          function name ({message, number = 128}) {
            return number;
          }
          
          const value = name({
            message : 'text'
          });
          
          console.log(value); // 128
          
          name( ); // Error
          

          Zudem können Standardwerte auch in Kombination mit der Syntax zur Destrukturierung von Arrays und Objekten verwendet werden, wie das Beispiel oben zeigt, bei dem die Werte fehlernder Eigenschaften eines als Argument übergebenen Objektes direkt ersetzt werden. Bei der Verwendung dieser Syntax ist allerdings zu berücksichtigen, dass in dem Fall, dass kein Objekt, beziehungsweise kein Array als Argument übergeben wurde, eine Ausnahme geworfen wird.

          function name ({number, string} = name.defaults) {
            console.log(string);
            return number;
          }
          
          name.defaults = {
            string : 'text',
            number : 32
          };
          
          const value = name( ); // text
          
          console.log(value); // 32
          

          Die bessere Vorgehensweise ist es also meiner Meinung nach, für den entsprechenden Parameter ein Objekt anzulegen, dass als Standardwert verwendet wird, wenn beim Aufruf kein Objekt als Argument übergeben wurde.

          const array = [64, 128];
          
          let [first, second, last = 256] = array;
          
          console.log(first + last); // 320
          

          Wie gesagt können Default Values aber auch bei der Destrukturierung von Arrays verwendet werden, sowohl bei der Deklaration von Parametern, als auch bei der Initialisierung von Konstanten und Variablen, und hier gelten dieselben Regeln. Die Zuweisung wird also durch undefined ausgelöst …

          let [fail] = harry; // Error
          

          … und in dem Fall, dass kein Array referenziert werden kann, wird eine Ausnahme geworfen.

          Naja, wie auch immer, ich denke man könnte hierzu noch einiges mehr schreiben, zu konkreten use cases ebenso wie zu den technischen Hintergründen, aber ich höre jetzt einfach mal auf, da es mir ja nur darum ging, eine kleine Übersicht zu der neuen Syntax zu geben, die im Zusammenhang mit diesem Thema relevant ist.


          Unter dem Strich bleibt also festzuhalten, dass die in ECMAScript 2015 hinzugekommenen Features doch deutlich ausdrucksstärkeren Code ermöglichen und dass ich es definitiv begrüßenswert fände, wenn die Unterstützung dieser Features bereits weiter verbreitet wäre. ;-)

          Viele Grüße,

          Orlok