Porozumění funkcím šipek v JavaScriptu

Tento článek byl původně napsán pro DigitalOcean.

Úvod

Vydání specifikace ECMAScript (ES6) z roku 2015 přidalo výrazy funkce šipky do jazyka JavaScript. Šipkové funkce představují nový způsob psaní anonymních výrazů funkcí a jsou podobné funkcím lambda v některých jiných programovacích jazycích, jako je Python.

Funkce šipek se od tradičních funkcí liší v mnoha ohledech, včetně způsobu, jakým je určen jejich rozsah a jak je vyjádřena jejich syntaxe. Z tohoto důvodu jsou funkce šipek zvláště užitečné při předávání funkce jako parametru funkci vyššího řádu, například když procházíte polem pomocí vestavěných metod iterátoru. Jejich syntaktická zkratka vám také může umožnit zlepšit čitelnost vašeho kódu.

V tomto článku si prohlédnete deklarace funkcí a výrazy, dozvíte se o rozdílech mezi tradičními funkčními výrazy a výrazy funkcí šipky, dozvíte se o lexikálním rozsahu, pokud jde o funkce šipky, a prozkoumáte některé syntaktické zkratky povolené u funkcí šipek.

Definování funkcí

Než se ponoříme do specifik výrazů funkcí šipek, tento tutoriál stručně zopakuje tradiční funkce JavaScriptu, abychom později lépe ukázali jedinečné aspekty funkcí šipek.

Výukový program Jak definovat funkce v JavaScriptu dříve v této sérii představil koncept deklarací funkcí a výrazy funkcí . Deklarace funkce je pojmenovaná funkce napsaná pomocí function klíčové slovo. Deklarace funkcí se načtou do kontextu provádění před spuštěním jakéhokoli kódu. Toto je známé jako zvedání , což znamená, že funkci můžete použít předtím, než ji deklarujete.

Zde je příklad sum funkce, která vrací součet dvou parametrů:

function sum(a, b) {
  return a + b
}

Můžete spustit sum funkce před deklarací funkce z důvodu zvedání:

sum(1, 2)

function sum(a, b) {
  return a + b
}

Spuštění tohoto kódu by poskytlo následující výstup:

3

Název funkce zjistíte po přihlášení samotné funkce:

console.log(sum)

To vrátí funkci spolu s jejím názvem:

ƒ sum(a, b) {
  return a + b
}

Výraz funkce je funkce, která není předem zavedena do kontextu provádění a spouští se pouze tehdy, když se s ní kód setká. Funkční výrazy jsou obvykle přiřazeny k proměnné a mohou být anonymní , což znamená, že funkce nemá jméno.

V tomto příkladu napište stejný sum funkce jako anonymní funkční výraz:

const sum = function (a, b) {
  return a + b
}

Nyní jste přiřadili anonymní funkci sum konstantní. Pokus o provedení funkce před jejím deklarováním bude mít za následek chybu:

sum(1, 2)

const sum = function (a, b) {
  return a + b
}

Spuštěním tohoto získáte:

Uncaught ReferenceError: Cannot access 'sum' before initialization

Všimněte si také, že funkce nemá pojmenovaný identifikátor. Pro ilustraci napište stejnou anonymní funkci přiřazenou k sum a poté zapište sum do konzole:

const sum = function (a, b) {
  return a + b
}

console.log(sum)

To vám ukáže následující:

ƒ (a, b) {
  return a + b
}

Hodnota sum je anonymní funkce, nikoli pojmenovaná funkce.

Výrazy funkcí zapsané pomocí function můžete pojmenovat klíčové slovo, ale v praxi to není populární. Jedním z důvodů, proč byste mohli chtít pojmenovat výraz funkce, je usnadnit ladění trasování zásobníku chyb.

Zvažte následující funkci, která používá if příkaz k vyvolání chyby, pokud parametry funkce chybí:

const sum = function namedSumFunction(a, b) {
  if (!a || !b) throw new Error('Parameters are required.')

  return a + b
}

sum()

Zvýrazněná část přiřadí funkci název a poté funkce použije nebo || operátor, který vyvolá chybový objekt, pokud některý z parametrů chybí.

Spuštěním tohoto kódu získáte následující:

Uncaught Error: Parameters are required.
    at namedSumFunction (<anonymous>:3:23)
    at <anonymous>:1:1

V tomto případě vám pojmenování funkce poskytne rychlou představu o tom, kde je chyba.

výraz funkce šipky je anonymní funkční výraz napsaný syntaxí "fat arrow" (=> ).

Přepište sum funkce se syntaxí funkce šipky:

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

Stejně jako tradiční výrazy funkcí nejsou funkce se šipkami zvednuty, takže je nemůžete volat dříve, než je deklarujete. Jsou také vždy anonymní – neexistuje způsob, jak funkci šipky pojmenovat. V další části prozkoumáte více syntaktických a praktických rozdílů mezi funkcemi šipek a tradičními funkcemi.

Funkce šipek

Funkce šipek mají několik důležitých rozdílů v tom, jak fungují, které je odlišují od tradičních funkcí, a také několik syntaktických vylepšení. Největší funkční rozdíly jsou v tom, že funkce šipek nemají vlastní this vazba nebo prototyp a nelze je použít jako konstruktor. Funkce šipek lze také napsat jako kompaktnější alternativu k tradičním funkcím, protože umožňují vynechat závorky kolem parametrů a přidat koncept stručného těla funkce s implicitním návratem.

V této části si projdete příklady, které ilustrují každý z těchto případů.

Lexikální this

Klíčové slovo this je v JavaScriptu často považováno za složité téma. Článek Understanding This, Bind, Call, and Apply in JavaScript vysvětluje, jak this funguje a jak this lze implicitně odvodit na základě toho, zda jej program používá v globálním kontextu, jako metodu v objektu, jako konstruktor funkce nebo třídy nebo jako obsluhu události DOM.

Funkce šipek mají lexikální this , což znamená hodnotu this je určeno okolním rozsahem (lexikálním prostředím).

Následující příklad bude demonstrovat rozdíl mezi tím, jak tradiční a šipkové funkce zacházejí s this . V následujícím printNumbers objekt, existují dvě vlastnosti:phrase a numbers . Na objektu je také metoda loop , který by měl vytisknout phrase řetězec a aktuální hodnotu v numbers :

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    this.numbers.forEach(function (number) {
      console.log(this.phrase, number)
    })
  },
}

Dalo by se očekávat loop funkce pro tisk řetězce a aktuálního čísla ve smyčce při každé iteraci. Nicméně ve výsledku spuštění funkce phrase je ve skutečnosti undefined :

printNumbers.loop()

Výsledkem bude následující:

undefined 1
undefined 2
undefined 3
undefined 4

Jak ukazuje toto, this.phrase je nedefinováno, což znamená, že this v rámci anonymní funkce předané do forEach metoda neodkazuje na printNumbers objekt. Je to proto, že tradiční funkce neurčuje jeho this hodnota z rozsahu prostředí, což je printNumbers objekt.

Ve starších verzích JavaScriptu byste museli použít bind metoda, která explicitně nastavuje this . Tento vzor lze často nalézt v některých dřívějších verzích frameworků, jako je React, před příchodem ES6.

Použijte bind pro opravu funkce:

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    // Bind the `this` from printNumbers to the inner forEach function
    this.numbers.forEach(
      function (number) {
        console.log(this.phrase, number)
      }.bind(this),
    )
  },
}

printNumbers.loop()

To poskytne očekávaný výsledek:

The current value is: 1
The current value is: 2
The current value is: 3
The current value is: 4

Funkce šipek může poskytnout přímější způsob, jak se s tím vypořádat. Od jejich this hodnota je určena na základě lexikálního rozsahu, vnitřní funkce volané v forEach nyní má přístup k vlastnostem vnějšího printNumbers objekt, jak je ukázáno:

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    this.numbers.forEach((number) => {
      console.log(this.phrase, number)
    })
  },
}

printNumbers.loop()

To poskytne očekávaný výsledek:

The current value is: 1
The current value is: 2
The current value is: 3
The current value is: 4

Tyto příklady ukazují, že použití funkcí šipek v integrovaných metodách pole, jako je forEach , map , filter a reduce může být intuitivnější a snáze čitelná, takže tato strategie s větší pravděpodobností splní očekávání.

Funkce šipek jako objektové metody

Zatímco funkce šipek jsou vynikající jako parametrické funkce předávané do metod polí, nejsou účinné jako objektové metody kvůli způsobu, jakým používají lexikální rozsah pro this . Použijte stejný příklad jako předtím, vezměte loop metodu a přeměňte ji na funkci šipky, abyste zjistili, jak se bude provádět:

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop: () => {
    this.numbers.forEach((number) => {
      console.log(this.phrase, number)
    })
  },
}

V tomto případě objektové metody this by měl odkazovat na vlastnosti a metody printNumbers objekt. Protože však objekt nevytváří nový lexikální rozsah, funkce šipky bude hledat za objektem hodnotu this .

Zavolejte loop() metoda:

printNumbers.loop()

Výsledkem bude následující:

Uncaught TypeError: Cannot read property 'forEach' of undefined

Protože objekt nevytváří lexikální rozsah, metoda funkce šipky hledá this ve vnějším rozsahu – Window v tomto příkladu. Od numbers vlastnost na Window neexistuje objekt, vyvolá chybu. Obecně platí, že je bezpečnější používat tradiční funkce jako objektové metody ve výchozím nastavení.

Funkce šipek nemají constructor nebo prototype

Výukový program Pochopení prototypů a dědičnosti v JavaScriptu dříve v této sérii vysvětlil, že funkce a třídy mají prototype vlastnost, kterou JavaScript používá jako plán pro klonování a dědění.

Pro ilustraci vytvořte funkci a zaznamenejte automaticky přiřazený prototype vlastnost:

function myFunction() {
  this.value = 5
}

// Log the prototype property of myFunction
console.log(myFunction.prototype)

Toto vytiskne na konzoli následující:

{constructor: ƒ}

To ukazuje, že v prototype vlastnost existuje objekt s constructor . To vám umožní používat new klíčové slovo pro vytvoření instance funkce:

const instance = new myFunction()

console.log(instance.value)

Výsledkem bude hodnota value vlastnost, kterou jste definovali při první deklaraci funkce:

5

Naproti tomu funkce šipky nemají prototype vlastnictví. Vytvořte novou funkci šipky a zkuste zaprotokolovat její prototyp:

const myArrowFunction = () => {}

// Attempt to log the prototype property of myArrowFunction
console.log(myArrowFunction.prototype)

Výsledkem bude následující:

undefined

V důsledku chybějícího prototype vlastnost, new klíčové slovo není dostupné a nemůžete vytvořit instanci z funkce šipky:

const arrowInstance = new myArrowFunction()

console.log(arrowInstance)

Zobrazí se následující chyba:

Uncaught TypeError: myArrowFunction is not a constructor

To je v souladu s naším dřívějším příkladem:Protože funkce šipek nemají vlastní this hodnotu, z toho vyplývá, že byste nemohli použít funkci šipky jako konstruktor.

Jak je zde ukázáno, funkce šipek mají mnoho jemných změn, díky nimž fungují odlišně od tradičních funkcí v ES5 a dřívějších. Došlo také k několika volitelným syntaktickým změnám, díky nimž je psaní funkcí šipek rychlejší a méně podrobné. Následující část ukáže příklady těchto změn syntaxe.

Implicitní návrat

Tělo tradiční funkce je obsaženo v bloku pomocí složených závorek ({} ) a skončí, když kód narazí na return klíčové slovo. Takto vypadá tato implementace jako funkce šipky:

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

Funkce šipek zavádí stručnou syntaxi těla nebo implicitní návrat . To umožňuje vynechání složených závorek a return klíčové slovo.

const sum = (a, b) => a + b

Implicitní návrat je užitečný pro vytváření stručných jednořádkových operací v map , filter a další běžné metody pole. Všimněte si, že jak závorky, tak return klíčové slovo musí být vynecháno. Pokud nemůžete napsat tělo jako jednořádkový návratový příkaz, budete muset použít normální syntaxi těla bloku.

V případě vrácení objektu syntaxe vyžaduje, abyste zalomili literál objektu do závorek. V opačném případě budou závorky považovány za tělo funkce a nevypočítají return hodnotu.

Pro ilustraci najděte následující příklad:

const sum = (a, b) => ({result: a + b})

sum(1, 2)

Výsledkem bude následující výstup:

{result: 3}

Vynechání závorek kolem jednoho parametru

Dalším užitečným syntaktickým vylepšením je možnost odstranit závorky kolem jednoho parametru ve funkci. V následujícím příkladu square funkce funguje pouze s jedním parametrem, x :

const square = (x) => x * x

V důsledku toho můžete vynechat závorky kolem parametru a bude to fungovat stejně:

const square = x => x * x

square(10)

Výsledkem bude následující:

100

Všimněte si, že pokud funkce nemá žádné parametry, budou vyžadovány závorky:

const greet = () => 'Hello!'

greet()

Volání greet() bude fungovat následovně:

'Hello!'

Některé kódové báze se rozhodnou vynechat závorky, kdykoli je to možné, a jiné se rozhodnou vždy zachovat závorky kolem parametrů bez ohledu na to, co se děje, zejména v kódových bázích, které používají TypeScript a vyžadují více informací o každé proměnné a parametru. Při rozhodování o tom, jak zapsat funkce šipek, se podívejte do průvodce stylem projektu, do kterého přispíváte.

Závěr

V tomto článku jste zkontrolovali tradiční funkce a rozdíl mezi deklaracemi funkcí a výrazy funkcí. Naučili jste se, že funkce šipek jsou vždy anonymní, nemají prototype nebo constructor , nelze použít s new klíčové slovo a určete hodnotu this přes lexikální rozsah. Nakonec jste prozkoumali nová syntaktická vylepšení dostupná pro funkce šipek, jako je implicitní návrat a vynechání závorek pro funkce s jedním parametrem.

Pro přehled základních funkcí si přečtěte Jak definovat funkce v JavaScriptu. Chcete-li si přečíst více o konceptu rozsahu a zvedání v JavaScriptu, přečtěte si článek Pochopení proměnných, rozsahu a zvedání v JavaScriptu.