Orlok: Parameter

Beitrag lesen

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