Arrow funkce

AndreaAndrea

V článku o funkcích jsme se seznámili s různými způsoby vytváření funkcí. Jedna z oblíbených věcí, která byla přidána do ES 2015 jsou Arrow funkce (známé jako "fat arrow funkce", "lambda funkce").

Tyto funkce jsou krátké a účelné a často se berou jakou syntaktický cukr k anonymním funkcím a také se běžně používají jako zkrácený zápis anonymní funkce.

Je dobré srovnání, ale není úplně přesné. Arrow funkce se ještě liší v několika dalších aspektech, jako je absence this a arguments a můžou mít implicitní návratovou hodnotu. Rozebereme si jednotlivé rozdíly.

Kratší a efektivnější syntaxe

Ukažme si definici běžné funkce:

function add(a, b) {
  return a + b;
}

Na první pohled je syntaxe arrow kratší a stručnější. Předchozí příklad se dá napsat takto:

const add = (a, b) => {
	return a + b;
}

Vidíte ten rozdíl, že nyní nemusíme používat klíčové slovo function pro definici funkce? To však není celé, můžeme pokračovat ve zjednodušování. Tento příklad je ekvivalentní předchozí funkci

const add = (a, b) => a + b;

Můžeme v tomto případě využít toho, že v těle funkce je jediný výraz. V takovém případě není ani třeba závorek a klíčového slova return. Tento způsob se nazývá implicitní return.

Pokud má funkce jediný parametr, můžete se zcela zbavit závorek kolem parametru. Může se nám zdát, že jde o bagatelní úpravu, nicméně během vyvíjení aplikací napíšeme takových funkcí minimálně stovky či tisíce.

Lexikální binding

Při programování v JS se nám běžně děje to, že skládáme funkce do sebe. Než byly do JS přidány arrow funkce, pak platilo, že se při každém volání funkce vytvořil její speciální this kontext a přidal se do jméného prostoru. Často se však hodí přistup k this rodičovské funkce. To se tradičně řešilo takhle:

// ES3
var that = this;
element.addEventListener('click', function(e) {
	this.foo();
})

V novějších prohlížečích pak bylo možné použít metodu bind() a předat správnou referenci na this.

// ES5
element.addEventListener('click', function(e) {
	this.foo();
}).bind(this)

Jelikož arrow funkce mají lexikální chování this, hodnota this se nemění a je lexikálně závislá na kontextu, ve kterém byla funkce definována. A tím se zelegantnilo řešení tohoto problému.

// Arrow Funkce v ES6
element.addEventListener('click', () => this.foo());

Chování arrow funkcí si také můžeme představit jako by automaticky bindovaly aktuální this hodnotu nadřazené funkce.

Nepodporuje arguments

V běžných funkcích můžeme použít objekt arguments^arguments mdn, který obsahuje seznam argumentů, s nimiž byla funkce volána. V arrow funkcích však arguments není dostupný. Místo toho bychom měli použít seznam argumentů, který je uveden v závorkách před šipkou.

Implicitní return

Použití arrow funkce je velice vhodné tam, kde je potřeba jednoduchá logika. Pokud tělo funkce obsahuje jediný výraz, nemusíme psát závorky a return protože výsledek výrazu se automaticky vrací jako výsledek funkce.

Nedají se použít "objektově"

Konstruktory jsou speciální typy funkcí, které jsou používány k vytvoření nových objektů. Arrow funkce nelze použít jako konstruktory a nemají ani vlastnost prototype. Sami si můžete ověřit v consoli následující kód:

(() => {}).prototype // undefined
(function() {}).prototype // {constructor: f}

Nevýhody

Čím kratší syntaxe je, tím složitější mohou být některé okrajové případy. Přeci jen máme několik variací jak tuto funkci psát - bez závorek, bez return apod.

Jediný způsob jak zjistímě co anonymní funkce dělá, je, že si přečteme její tělo. Kdybysme použili pojmenované funkce, tak by nám mohlo práci usnadnit přečíst si název funkce, který může být přesnější a semantičtější.

const emails = people.map(getPersonEmail)

const emails2 = people.map((p) => p.electronicInvoicingContact)

Musíme tedy při čtení kódu víc domýšlet a odvozovat.

Kdy je vhodnější použít arrow funkci místo jiné funkce?

Uplatní se v podobných místech, kde píšete callbacky nebo jako argumenty pro jiné funkce (higher-order funkce). Příklad použití:

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

Jak jsme naznačili v předchozím odstavci, tyto funkce jsou obvykle one-line funkce s jednoduchou logikou. Hodí používat v místech, kde je logika triviální, naprosto zřejmá a kód samovysvětlující.

Pokud ve funkcích nepoužíváte this nebo arguments mohou být velmi elegantním a čitelným řešením.

Tedy přesuňme se k praktickým příkladům.

Příklad použití arrow funkce jako metody objektu:

const person = {
  name: 'John',
  sayHello: () => console.log(`Hello, my name is ${this.name}`)
};
person.sayHello(); // Hello, my name is

Pokud je kód závislý na dynamické hodnotě this, buďte uvědomělí v tom, ke kterému this máte přístup!

Například

Game.prototype.restart = function () {
    this.clearLocalStorage();
    this.timer = setTimeout(function() {
		this.clearBoard();    // Co je tady "this"?
    }, 0);
};

Spuštění uvedeného kódu vede k následující chybě: Uncaught TypeError: undefined is not a function Proč? Jde o kontext. Výše uvedená chyba se objevuje proto, že při volání funkce setTimeout() ve skutečnosti voláte funkci window.setTimeout(). Výsledkem je, že anonymní funkce předávaná do setTimeout() je definována v kontextu objektu window, který nemá metodu clearBoard().