function mit beliebig vielen Übergabeparametern
piet
- javascript
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
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.
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
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.
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