...)undefined Standardwerte aus?arguments mehr!...)Die Parameterbehandlung wurde in ECMAScript 6 erheblich erweitert. Sie unterstützt nun Standard-Parameterwerte, Rest-Parameter (Varargs) und Destrukturierung.
Zusätzlich hilft der Spread-Operator bei Funktions-/Methoden-/Konstruktoraufrufen und Array-Literalen.
Ein Standard-Parameterwert wird für einen Parameter mit einem Gleichheitszeichen (=) angegeben. Wenn ein Aufrufer keinen Wert für den Parameter angibt, wird der Standardwert verwendet. Im folgenden Beispiel ist der Standard-Parameterwert von y 0
function func(x, y=0) {
return [x, y];
}
func(1, 2); // [1, 2]
func(1); // [1, 0]
func(); // [undefined, 0]
Wenn Sie einem Parameternamen den Rest-Operator (...) voranstellen, erhält dieser Parameter alle verbleibenden Parameter als Array
function format(pattern, ...params) {
return {pattern, params};
}
format(1, 2, 3);
// { pattern: 1, params: [ 2, 3 ] }
format();
// { pattern: undefined, params: [] }
Sie können benannte Parameter simulieren, wenn Sie in der Parameterliste mit einem Objekt-Muster eine Destrukturierung durchführen
function selectEntries({ start=0, end=-1, step=1 } = {}) { // (A)
// The object pattern is an abbreviation of:
// { start: start=0, end: end=-1, step: step=1 }
// Use the variables `start`, `end` and `step` here
···
}
selectEntries({ start: 10, end: 30, step: 2 });
selectEntries({ step: 3 });
selectEntries({});
selectEntries();
Das = {} in Zeile A ermöglicht es Ihnen, selectEntries() ohne Parameter aufzurufen.
...) In Funktions- und Konstruktoraufrufen wandelt der Spread-Operator iterierbare Werte in Argumente um
> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
> Math.max(-1, ...[-5, 11], 3)
11
In Array-Literalen wandelt der Spread-Operator iterierbare Werte in Array-Elemente um
> [1, ...[2,3], 4]
[1, 2, 3, 4]
Der ES6-Weg der Parameterbehandlung ist äquivalent zur Destrukturierung der tatsächlichen Parameter über die formalen Parameter. Das heißt, der folgende Funktionsaufruf
function func(«FORMAL_PARAMETERS») {
«CODE»
}
func(«ACTUAL_PARAMETERS»);
ist ungefähr äquivalent zu
{
let [«FORMAL_PARAMETERS»] = [«ACTUAL_PARAMETERS»];
{
«CODE»
}
}
Beispiel – der folgende Funktionsaufruf
function logSum(x=0, y=0) {
console.log(x + y);
}
logSum(7, 8);
wird zu
{
let [x=0, y=0] = [7, 8];
{
console.log(x + y);
}
}
Schauen wir uns nun spezifische Features an.
ECMAScript 6 ermöglicht es Ihnen, Standardwerte für Parameter anzugeben
function f(x, y=0) {
return [x, y];
}
Das Weglassen des zweiten Parameters löst den Standardwert aus
> f(1)
[1, 0]
> f()
[undefined, 0]
Vorsicht – undefined löst ebenfalls den Standardwert aus
> f(undefined, undefined)
[undefined, 0]
Der Standardwert wird bei Bedarf berechnet, nur wenn er tatsächlich benötigt wird
> const log = console.log.bind(console);
> function g(x=log('x'), y=log('y')) {return 'DONE'}
> g()
x
y
'DONE'
> g(1)
y
'DONE'
> g(1, 2)
'DONE'
undefined Standardwerte aus? Es ist nicht sofort ersichtlich, warum undefined als fehlender Parameter oder fehlender Teil eines Objekts oder Arrays interpretiert werden sollte. Der Grund dafür ist, dass es Ihnen ermöglicht, die Definition von Standardwerten zu delegieren. Betrachten wir zwei Beispiele.
Im ersten Beispiel (Quelle: Rick Waldrons TC39-Meeting-Notizen vom 24.07.2012) müssen wir keinen Standardwert in setOptions() definieren, wir können diese Aufgabe an setLevel() delegieren.
function setLevel(newLevel = 0) {
light.intensity = newLevel;
}
function setOptions(options) {
// Missing prop returns undefined => use default
setLevel(options.dimmerLevel);
setMotorSpeed(options.speed);
···
}
setOptions({speed:5});
Im zweiten Beispiel muss square() keinen Standardwert für x definieren, es kann diese Aufgabe an multiply() delegieren
function multiply(x=1, y=1) {
return x * y;
}
function square(x) {
return multiply(x, x);
}
Standardwerte verankern die Rolle von undefined weiter als Indikator für das Nichtvorhandensein, im Gegensatz zu null, das für Leere steht.
Innerhalb eines Standard-Parameterwerts können Sie auf jede Variable, einschließlich anderer Parameter, verweisen
function foo(x=3, y=x) {}
foo(); // x=3; y=3
foo(7); // x=7; y=7
foo(7, 2); // x=7; y=2
Die Reihenfolge ist jedoch wichtig. Parameter werden von links nach rechts deklariert. „Innerhalb“ eines Standardwerts erhalten Sie einen ReferenceError, wenn Sie auf einen Parameter zugreifen, der noch nicht deklariert wurde
function bar(x=y, y=4) {}
bar(3); // OK
bar(); // ReferenceError: y is not defined
Standardwerte existieren in ihrem eigenen Gültigkeitsbereich, der zwischen dem „äußeren“ Gültigkeitsbereich, der die Funktion umgibt, und dem „inneren“ Gültigkeitsbereich des Funktionskörpers liegt. Daher können Sie von den Standardwerten aus nicht auf „innere“ Variablen zugreifen
const x = 'outer';
function foo(a = x) {
const x = 'inner';
console.log(a); // outer
}
Wenn im vorherigen Beispiel kein äußeres x vorhanden wäre, würde der Standardwert x einen ReferenceError erzeugen (falls ausgelöst).
Diese Einschränkung ist wahrscheinlich am überraschendsten, wenn Standardwerte Closures sind
const QUX = 2;
function bar(callback = () => QUX) { // returns 2
const QUX = 3;
callback();
}
bar(); // ReferenceError
Wenn Sie den Rest-Operator (...) vor den letzten formalen Parameter setzen, bedeutet dies, dass dieser alle verbleibenden tatsächlichen Parameter in einem Array erhält.
function f(x, ...y) {
···
}
f('a', 'b', 'c'); // x = 'a'; y = ['b', 'c']
Wenn keine verbleibenden Parameter vorhanden sind, wird der Rest-Parameter auf das leere Array gesetzt
f(); // x = undefined; y = []
arguments mehr! Rest-Parameter können die berüchtigte spezielle Variable arguments von JavaScript vollständig ersetzen. Sie haben den Vorteil, dass sie immer Arrays sind
// ECMAScript 5: arguments
function logAllArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
// ECMAScript 6: rest parameter
function logAllArguments(...args) {
for (const arg of args) {
console.log(arg);
}
}
Ein interessantes Merkmal von arguments ist, dass Sie normale Parameter und ein Array aller Parameter gleichzeitig haben können
function foo(x=0, y=0) {
console.log('Arity: '+arguments.length);
···
}
Sie können arguments in solchen Fällen vermeiden, wenn Sie einen Rest-Parameter mit Array-Destrukturierung kombinieren. Der resultierende Code ist länger, aber expliziter
function foo(...args) {
let [x=0, y=0] = args;
console.log('Arity: '+args.length);
···
}
Die gleiche Technik funktioniert für benannte Parameter (Optionsobjekte)
function bar(options = {}) {
let { namedParam1, namedParam2 } = options;
···
if ('extra' in options) {
···
}
}
arguments ist iterierbar arguments ist in ECMAScript 6 iterierbar5, was bedeutet, dass Sie for-of und den Spread-Operator verwenden können
> (function () { return typeof arguments[Symbol.iterator] }())
'function'
> (function () { return Array.isArray([...arguments]) }())
true
Beim Aufrufen einer Funktion (oder Methode) in einer Programmiersprache müssen Sie die tatsächlichen Parameter (vom Aufrufer bereitgestellt) den formalen Parametern (einer Funktionsdefinition) zuordnen. Es gibt zwei gängige Möglichkeiten, dies zu tun
selectEntries(3, 20, 2)
selectEntries({ start: 3, end: 20, step: 2 })
Benannte Parameter haben zwei Hauptvorteile: Sie liefern Beschreibungen für Argumente in Funktionsaufrufen und sie eignen sich gut für optionale Parameter. Ich werde zuerst die Vorteile erklären und Ihnen dann zeigen, wie Sie benannte Parameter in JavaScript über Objektliterale simulieren können.
Sobald eine Funktion mehr als einen Parameter hat, können Sie verwirrt sein, wofür jeder Parameter verwendet wird. Nehmen wir zum Beispiel eine Funktion, selectEntries(), die Einträge aus einer Datenbank zurückgibt. Angesichts des Funktionsaufrufs
selectEntries(3, 20, 2);
was bedeuten diese drei Zahlen? Python unterstützt benannte Parameter, und sie machen es einfach, zu verstehen, was vor sich geht
# Python syntax
selectEntries(start=3, end=20, step=2)
Optionale Positions-Parameter funktionieren nur dann gut, wenn sie am Ende weggelassen werden. Irgendwo anders müssen Sie Platzhalter wie null einfügen, damit die verbleibenden Parameter die richtigen Positionen haben.
Bei optionalen benannten Parametern ist das kein Problem. Sie können jeden davon leicht weglassen. Hier sind einige Beispiele
# Python syntax
selectEntries(step=2)
selectEntries(end=20, start=3)
selectEntries()
JavaScript hat im Gegensatz zu Python und vielen anderen Sprachen keine native Unterstützung für benannte Parameter. Aber es gibt eine recht elegante Simulation: Jeder tatsächliche Parameter ist eine Eigenschaft in einem Objektliteral, dessen Ergebnis als einzelner formaler Parameter an den Aufgerufenen übergeben wird. Wenn Sie diese Technik verwenden, sieht ein Aufruf von selectEntries() wie folgt aus.
selectEntries({ start: 3, end: 20, step: 2 });
Die Funktion empfängt ein Objekt mit den Eigenschaften start, end und step. Sie können jede davon weglassen
selectEntries({ step: 2 });
selectEntries({ end: 20, start: 3 });
selectEntries();
In ECMAScript 5 würden Sie selectEntries() wie folgt implementieren
function selectEntries(options) {
options = options || {};
var start = options.start || 0;
var end = options.end || -1;
var step = options.step || 1;
···
}
In ECMAScript 6 können Sie Destrukturierung verwenden, die wie folgt aussieht
function selectEntries({ start=0, end=-1, step=1 }) {
···
}
Wenn Sie selectEntries() mit null Argumenten aufrufen, schlägt die Destrukturierung fehl, da Sie kein Objektmuster gegen undefined abgleichen können. Das kann durch einen Standardwert behoben werden. Im folgenden Code wird das Objektmuster gegen {} abgeglichen, wenn der erste Parameter fehlt.
function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
}
Sie können auch Positions-Parameter mit benannten Parametern kombinieren. Es ist üblich, dass letztere zuletzt kommen
someFunc(posArg1, { namedArg1: 7, namedArg2: true });
Im Prinzip könnten JavaScript-Engines dieses Muster optimieren, sodass kein Zwischenobjekt erstellt wird, da sowohl die Objektliterale an den Aufrufstellen als auch die Objektmuster in den Funktionsdefinitionen statisch sind.
Sie werden in ECMAScript 6 wahrscheinlich am häufigsten die for-of-Schleife verwenden, aber die Array-Methode forEach() profitiert ebenfalls von der Destrukturierung. Oder besser gesagt, ihr Callback tut dies.
Erstes Beispiel: Destrukturierung der Arrays in einem Array.
const items = [ ['foo', 3], ['bar', 9] ];
items.forEach(([word, count]) => {
console.log(word+' '+count);
});
Zweites Beispiel: Destrukturierung der Objekte in einem Array.
const items = [
{ word:'foo', count:3 },
{ word:'bar', count:9 },
];
items.forEach(({word, count}) => {
console.log(word+' '+count);
});
Eine ECMAScript 6 Map hat keine Methode map() (wie Arrays). Daher muss man
[key,value]-Paaren umwandeln.map() transformieren.Das sieht wie folgt aus.
const map0 = new Map([
[1, 'a'],
[2, 'b'],
[3, 'c'],
]);
const map1 = new Map( // step 3
[...map0] // step 1
.map(([k, v]) => [k*2, '_'+v]) // step 2
);
// Resulting Map: {2 -> '_a', 4 -> '_b', 6 -> '_c'}
Die Tool-Methode Promise.all() funktioniert wie folgt
Destrukturierung hilft bei der Verarbeitung des Arrays, mit dem das Ergebnis von Promise.all() erfüllt wird
const urls = [
'http://example.com/foo.html',
'http://example.com/bar.html',
'http://example.com/baz.html',
];
Promise.all(urls.map(downloadUrl))
.then(([fooStr, barStr, bazStr]) => {
···
});
// This function returns a Promise that is fulfilled
// with a string (the text)
function downloadUrl(url) {
return fetch(url).then(request => request.text());
}
fetch() ist eine Promise-basierte Version von XMLHttpRequest. Es ist Teil des Fetch-Standards.
Dieser Abschnitt erwähnt einige Tricks für aussagekräftige Parameterdefinitionen. Sie sind clever, haben aber auch Nachteile: Sie fügen visuelles Durcheinander hinzu und können Ihren Code schwerer verständlich machen.
Einige Parameter haben keine Standardwerte, können aber weggelassen werden. In diesem Fall verwende ich gelegentlich den Standardwert undefined, um deutlich zu machen, dass der Parameter optional ist. Das ist redundant, aber beschreibend.
function foo(requiredParam, optionalParam = undefined) {
···
}
In ECMAScript 5 haben Sie einige, aber eher umständliche Optionen, um sicherzustellen, dass ein erforderlicher Parameter übergeben wurde
function foo(mustBeProvided) {
if (arguments.length < 1) {
throw new Error();
}
if (! (0 in arguments)) {
throw new Error();
}
if (mustBeProvided === undefined) {
throw new Error();
}
···
}
In ECMAScript 6 können Sie Standard-Parameterwerte (miss-)brauchen, um prägnanteren Code zu erhalten (Verdient: Idee von Allen Wirfs-Brock)
/**
* Called if a parameter is missing and
* the default value is evaluated.
*/
function mandatory() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = mandatory()) {
return mustBeProvided;
}
Interaktion
> foo()
Error: Missing parameter
> foo(123)
123
Dieser Abschnitt präsentiert drei Ansätze, um eine maximale Arity zu erzwingen. Das laufende Beispiel ist eine Funktion f, deren maximale Arity 2 beträgt – wenn ein Aufrufer mehr als 2 Parameter übergibt, soll ein Fehler ausgelöst werden.
Der erste Ansatz sammelt alle tatsächlichen Parameter im formalen Rest-Parameter args und prüft dessen Länge.
function f(...args) {
if (args.length > 2) {
throw new Error();
}
// Extract the real parameters
let [x, y] = args;
}
Der zweite Ansatz setzt darauf, dass unerwünschte tatsächliche Parameter im formalen Rest-Parameter empty erscheinen.
function f(x, y, ...empty) {
if (empty.length > 0) {
throw new Error();
}
}
Der dritte Ansatz verwendet einen Sentinel-Wert, der verschwindet, wenn ein dritter Parameter vorhanden ist. Ein Vorbehalt ist, dass der Standardwert OK auch dann ausgelöst wird, wenn ein dritter Parameter mit dem Wert undefined vorhanden ist.
const OK = Symbol();
function f(x, y, arity=OK) {
if (arity !== OK) {
throw new Error();
}
}
Leider führt jede dieser Methoden zu erheblichem visuellen und konzeptionellen Durcheinander. Ich bin versucht, die Überprüfung von arguments.length zu empfehlen, aber ich möchte auch, dass arguments verschwindet.
function f(x, y) {
if (arguments.length > 2) {
throw new Error();
}
}
...) Der Spread-Operator (...) sieht genauso aus wie der Rest-Operator, ist aber sein Gegenteil
Math.max() ist ein gutes Beispiel, um zu demonstrieren, wie der Spread-Operator in Methodenaufrufen funktioniert. Math.max(x1, x2, ···) gibt das Argument mit dem größten Wert zurück. Es akzeptiert eine beliebige Anzahl von Argumenten, kann aber nicht auf Arrays angewendet werden. Der Spread-Operator behebt das
> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
Im Gegensatz zum Rest-Operator können Sie den Spread-Operator überall in einer Sequenz von Teilen verwenden
> Math.max(-1, ...[5, 11], 3)
11
Ein weiteres Beispiel ist, dass JavaScript keine Möglichkeit hat, die Elemente eines Arrays destruktiv an ein anderes anzuhängen. Arrays haben jedoch die Methode push(x1, x2, ···), die alle ihre Argumente an ihren Empfänger anhängt. Der folgende Code zeigt, wie Sie push() verwenden können, um die Elemente von arr2 an arr1 anzuhängen.
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];
arr1.push(...arr2);
// arr1 is now ['a', 'b', 'c', 'd']
Zusätzlich zu Funktions- und Methodenaufrufen funktioniert der Spread-Operator auch für Konstruktoraufrufe
new Date(...[1912, 11, 24]) // Christmas Eve 1912
Das ist etwas, das in ECMAScript 5 schwierig zu erreichen ist.
Der Spread-Operator kann auch innerhalb von Array-Literalen verwendet werden
> [1, ...[2,3], 4]
[1, 2, 3, 4]
Dies bietet eine bequeme Möglichkeit, Arrays zu verketten
const x = ['a', 'b'];
const y = ['c'];
const z = ['d', 'e'];
const arr = [...x, ...y, ...z]; // ['a', 'b', 'c', 'd', 'e']
Ein Vorteil des Spread-Operators ist, dass sein Operand ein beliebiges iterierbares Wert sein kann (im Gegensatz zur Array-Methode concat(), die keine Iteration unterstützt).
Der Spread-Operator ermöglicht es Ihnen, jeden iterierbaren Wert in ein Array zu konvertieren
const arr = [...someIterableObject];
Konvertieren wir ein Set in ein Array
const set = new Set([11, -1, 6]);
const arr = [...set]; // [11, -1, 6]
Ihre eigenen iterierbaren Objekte können auf die gleiche Weise in Arrays konvertiert werden
const obj = {
* [Symbol.iterator]() {
yield 'a';
yield 'b';
yield 'c';
}
};
const arr = [...obj]; // ['a', 'b', 'c']
Beachten Sie, dass der Spread-Operator, genau wie die for-of-Schleife, nur für iterierbare Werte funktioniert. Alle integrierten Datenstrukturen sind iterierbar: Arrays, Maps und Sets. Alle Array-ähnlichen DOM-Datenstrukturen sind ebenfalls iterierbar.
Sollten Sie jemals auf etwas stoßen, das nicht iterierbar, aber Array-ähnlich ist (indizierte Elemente plus eine length-Eigenschaft), können Sie Array.from()6 verwenden, um es in ein Array zu konvertieren
const arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ECMAScript 5:
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ECMAScript 6:
const arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
// TypeError: Cannot spread non-iterable value
const arr3 = [...arrayLike];