Arrow Functions (Pfeilfunktionen) wurden mit ECMAScript 2015 (ES6)
eingeführt und bieten eine kompaktere Syntax für die Funktionsdefinition
in JavaScript. Darüber hinaus bringen sie eine wichtige Besonderheit
mit: ein lexikalisch gebundenes this. Dieser Abschnitt
erläutert, wie Arrow Functions funktionieren, wie sie sich von regulären
Funktionen unterscheiden und wie ihr lexikalisches
this-Verhalten in der Praxis genutzt werden kann.
Die grundlegende Syntax einer Arrow Function ist:
// Traditionelle Funktionsdeklaration
function add(a, b) {
return a + b;
}
// Funktionsausdruck
const addExpression = function(a, b) {
return a + b;
};
// Arrow Function
const addArrow = (a, b) => {
return a + b;
};
// Verkürzte Syntax, wenn nur eine Anweisung vorhanden ist
const addShort = (a, b) => a + b;Arrow Functions bieten verschiedene Syntaxvereinfachungen:
// Ein Parameter ohne Klammern (nur bei einem einzigen einfachen Parameter möglich)
const square = x => x * x;
// Leere Parameterliste benötigt Klammern
const getRandomNumber = () => Math.random();
// Rückgabe eines Objektliterals erfordert Klammern
const createPerson = (name, age) => ({ name, age });
// Mehrere Anweisungen erfordern geschweifte Klammern und explizites return
const processData = data => {
const result = data.map(item => item * 2);
return result.filter(item => item > 10);
};thisDer wichtigste Unterschied zwischen Arrow Functions und regulären
Funktionen ist, wie sie mit dem this-Kontext umgehen.
this in
regulären FunktionenIn einer regulären Funktion wird this dynamisch gebunden
und hängt davon ab, wie die Funktion aufgerufen wird:
function regularFunction() {
console.log(this);
}
// Als Methode eines Objekts
const obj = {
name: 'Beispielobjekt',
method: regularFunction
};
regularFunction(); // `this` ist das globale Objekt (in Nicht-Strict-Mode) oder undefined (in Strict-Mode)
obj.method(); // `this` ist obj
new regularFunction(); // `this` ist ein neues Objekt
regularFunction.call({x: 10}); // `this` ist {x: 10}Dieses dynamische Verhalten von this kann verwirrend
sein und zu Fehlern führen, besonders in Callbacks und verschachtelten
Funktionen.
this in Arrow
FunctionsIm Gegensatz dazu haben Arrow Functions kein eigenes
this. Sie erben den this-Wert aus dem
umgebenden lexikalischen Kontext (der umgebenden Funktion oder dem
globalen Scope):
function outer() {
console.log('outer this:', this);
// Arrow Function erbt `this` vom umgebenden Kontext
const arrowFunction = () => {
console.log('arrow this:', this); // Gleicher Wert wie in outer()
};
// Reguläre Funktion hat ein eigenes `this`
function regularInner() {
console.log('regular inner this:', this); // Globales Objekt oder undefined
}
arrowFunction();
regularInner();
}
const obj = { name: 'Beispiel' };
outer.call(obj); // Setzt `this` in outer() auf objDie Ausgabe dieses Codes wäre:
outer this: { name: 'Beispiel' }
arrow this: { name: 'Beispiel' }
regular inner this: [globales Objekt oder undefined]
Ein häufiges Problem in JavaScript ist der Verlust des
this-Kontexts in Callbacks:
// Mit regulären Funktionen
class Timer {
constructor() {
this.seconds = 0;
this.intervalId = null;
}
start() {
// `this` verliert hier seinen Kontext
this.intervalId = setInterval(function() {
this.seconds++; // Fehler: `this` bezieht sich auf das globale Objekt
console.log(this.seconds);
}, 1000);
}
stop() {
clearInterval(this.intervalId);
}
}
// Traditionelle Lösungen:
// 1. Selbstreferenz (that/self/_this)
start() {
const that = this;
this.intervalId = setInterval(function() {
that.seconds++; // Funktioniert mit der gespeicherten Referenz
console.log(that.seconds);
}, 1000);
}
// 2. Function.prototype.bind()
start() {
this.intervalId = setInterval(function() {
this.seconds++;
console.log(this.seconds);
}.bind(this), 1000);
}Mit Arrow Functions lässt sich dieses Problem elegant lösen:
class Timer {
constructor() {
this.seconds = 0;
this.intervalId = null;
}
start() {
// Arrow Function behält den `this`-Kontext bei
this.intervalId = setInterval(() => {
this.seconds++; // Funktioniert korrekt, `this` bezieht sich auf die Timer-Instanz
console.log(this.seconds);
}, 1000);
}
stop() {
clearInterval(this.intervalId);
}
}Arrow Functions sind besonders nützlich für Event-Handler in Klassen oder Objekten:
class ClickCounter {
constructor() {
this.count = 0;
this.button = document.getElementById('counter-button');
// Arrow Function für Event-Handler
this.button.addEventListener('click', () => {
this.count++;
this.updateDisplay();
});
}
updateDisplay() {
document.getElementById('count-display').textContent = this.count;
}
}
// Verwendung
const counter = new ClickCounter();Arrow Functions sind aufgrund ihrer Kompaktheit ideal für funktionale Array-Methoden:
const zahlen = [1, 2, 3, 4, 5];
// map mit Arrow Function
const verdoppelt = zahlen.map(n => n * 2);
// [2, 4, 6, 8, 10]
// filter mit Arrow Function
const geradeZahlen = zahlen.filter(n => n % 2 === 0);
// [2, 4]
// reduce mit Arrow Function
const summe = zahlen.reduce((acc, n) => acc + n, 0);
// 15
// Verkettung mehrerer Methoden
const summeQuadrate = zahlen
.map(n => n * n)
.filter(n => n > 10)
.reduce((acc, n) => acc + n, 0);
// 16 + 25 = 41Arrow Functions können auch als sofort ausgeführte Funktionsausdrücke (IIFE) verwendet werden:
// IIFE mit Arrow Function
const ergebnis = (() => {
const temp = someComplexComputation();
return temp * 2;
})();Arrow Functions sind nicht in allen Szenarien geeignet. Hier sind einige wichtige Einschränkungen:
this-KontextDas kann sowohl ein Vorteil als auch ein Nachteil sein:
const obj = {
data: 42,
// Reguläre Funktion als Methode (empfohlen für Objektmethoden)
regularMethod() {
console.log(this.data); // 42
},
// Arrow Function als Methode (nicht empfohlen)
arrowMethod: () => {
console.log(this.data); // undefined, da this auf den umgebenden Kontext verweist
}
};arguments-ObjektArrow Functions haben keinen Zugriff auf das
arguments-Objekt:
function regular() {
console.log(arguments); // [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
const arrow = () => {
console.log(arguments); // ReferenceError oder bezieht sich auf arguments des umgebenden Scopes
};
regular(1, 2, 3);
arrow(1, 2, 3); // Fehler oder unerwartetes VerhaltenStattdessen kann der Rest-Parameter verwendet werden:
const arrowWithRest = (...args) => {
console.log(args); // [1, 2, 3]
};
arrowWithRest(1, 2, 3);Arrow Functions können nicht mit dem new-Operator
aufgerufen werden:
const Person = (name) => {
this.name = name;
};
const max = new Person('Max'); // TypeError: Person is not a constructorsuper-ZugriffArrow Functions haben keinen Zugriff auf super:
class Parent {
sayHello() {
return 'Hello from Parent';
}
}
class Child extends Parent {
// Reguläre Methode kann super verwenden
sayHello() {
return super.sayHello() + ' and Child';
}
// Arrow Function kann nicht direkt auf super zugreifen
arrowHello = () => {
// super.sayHello(); // Syntax-Fehler oder greift auf super des umgebenden Kontexts zu
}
}yield-UnterstützungArrow Functions können nicht als Generatoren verwendet werden:
// Regulärer Generator
function* regularGenerator() {
yield 1;
yield 2;
}
// Arrow Function kann kein Generator sein
const arrowGenerator = *() => { // Syntax-Fehler
yield 1;
yield 2;
};Für einfache, kurze Funktionen:
const double = x => x * 2;Für Callbacks, bei denen der umgebende
this-Kontext beibehalten werden soll:
button.addEventListener('click', () => {
this.handleClick();
});Für funktionale Programmierung mit Array-Methoden:
array.map(item => transformItem(item));Um Funktionen höherer Ordnung zurückzugeben:
const multiplier = factor => number => number * factor;Für Objektmethoden:
const obj = {
value: 42,
getValue() {
return this.value;
}
};Wenn this dynamisch gebunden werden
soll:
function clickHandler() {
console.log(this.textContent);
}
document.querySelectorAll('button').forEach(function(button) {
button.addEventListener('click', clickHandler);
});Für Konstruktorfunktionen:
function Person(name) {
this.name = name;
}Für Funktionen, die das arguments-Objekt
benötigen:
function sum() {
return Array.from(arguments).reduce((total, num) => total + num, 0);
}Für rekursive Funktionen mit Namen:
function factorial(n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}Arrow Functions eignen sich hervorragend für partielle Anwendung und Currying:
// Currying mit Arrow Functions
const add = a => b => a + b;
const add5 = add(5);
console.log(add5(3)); // 8
// Mehrstufiges Currying
const between = min => max => value => value >= min && value <= max;
const isAdult = between(18)(130);
console.log(isAdult(21)); // true
console.log(isAdult(16)); // falseArrow Functions eignen sich gut für die Erstellung von Hilfsfunktionen zur Funktionskomposition:
const compose = (...fns) => x => fns.reduceRight((value, fn) => fn(value), x);
const pipe = (...fns) => x => fns.reduce((value, fn) => fn(value), x);
// Beispiel
const double = x => x * 2;
const square = x => x * x;
const addOne = x => x + 1;
const transform1 = compose(addOne, square, double); // addOne(square(double(x)))
const transform2 = pipe(double, square, addOne); // addOne(square(double(x)))
console.log(transform1(3)); // 3 * 2 = 6, 6 * 6 = 36, 36 + 1 = 37
console.log(transform2(3)); // 3 * 2 = 6, 6 * 6 = 36, 36 + 1 = 37Arrow Functions können als Tag-Funktionen für Template Literals verwendet werden:
const highlight = (strings, ...values) => {
let result = '';
strings.forEach((string, i) => {
result += string;
if (i < values.length) {
result += `<span class="highlight">${values[i]}</span>`;
}
});
return result;
};
const name = 'JavaScript';
const html = highlight`Lernen Sie ${name} mit unseren Tutorials!`;
// "Lernen Sie <span class="highlight">JavaScript</span> mit unseren Tutorials!"Arrow Functions vereinfachen asynchronen Code mit Promises und async/await:
// Mit Promises
fetchData()
.then(data => processData(data))
.then(result => {
console.log(result);
return formatResult(result);
})
.catch(error => {
console.error('Fehler:', error);
return defaultResult;
});
// Mit async/await
const loadAndProcessData = async () => {
try {
const data = await fetchData();
const result = await processData(data);
console.log(result);
return formatResult(result);
} catch (error) {
console.error('Fehler:', error);
return defaultResult;
}
};this und KlassenpropertiesIn modernen JavaScript-Klassen können Arrow Functions für
Klassenproperties verwendet werden, um Methoden mit stabilem
this-Kontext zu definieren:
class Counter {
constructor() {
this.count = 0;
}
// Reguläre Methode
increment() {
this.count++;
}
// Arrow Function als Klassenproperty (benötigt Babel oder TypeScript)
decrement = () => {
this.count--;
}
// Beide können als Event-Handler verwendet werden, aber mit unterschiedlichem Verhalten
setupEventListeners() {
// Erfordert bind oder einen Wrapper
document.getElementById('inc').addEventListener('click', this.increment.bind(this));
// Funktioniert direkt, da this lexikalisch gebunden ist
document.getElementById('dec').addEventListener('click', this.decrement);
}
}Die Wahl zwischen Arrow Functions und regulären Funktionen hat in den meisten Fällen keine signifikanten Performance-Auswirkungen. Moderne JavaScript-Engines optimieren beide Formen sehr gut.
Es gibt jedoch einige Nuancen zu beachten:
Objektmethoden: In bestimmten Engines kann die Verwendung von regulären Funktionsdeklarationen für Objektmethoden leicht performanter sein als Arrow Functions.
arguments-Objekt: Die Verwendung
von Rest-Parametern anstelle des arguments-Objekts kann in
einigen Kontexten performanter sein.
Prototyp-Methoden vs. Instanz-Methoden: Arrow Functions als Klassenproperties erzeugen eine Methode pro Instanz, während reguläre Klassenmethoden auf dem Prototyp liegen:
class RegularMethods {
regular() { return this; }
}
class ArrowMethods {
arrow = () => this;
}
// regular() wird auf dem Prototyp gespeichert (effizienter für viele Instanzen)
// arrow() wird für jede Instanz neu erstellt (mehr Speicherverbrauch)