ES6 Funkcje strzałek: Fat and Concise Syntax in JavaScript

W tym artykule dowiesz się wszystkiego o składni funkcji strzałek w JavaScript – w tym o niektórych problemach, których musisz być świadomy, gdy wykorzystujesz funkcje strzałek w swoim kodzie. Zobaczysz wiele przykładów, które ilustrują ich działanie.

Funkcje strzałek zostały wprowadzone do JavaScriptu wraz z wydaniem ECMAScript 2015, znanego również jako ES6. Ich zwięzła składnia oraz sposób, w jaki obsługują słowo kluczowe this, są jednymi z głównych cech, które przyczyniły się do znacznego sukcesu funkcji strzałkowych wśród programistów.

Turning a Pre-ES6 Function into an Arrow Function

Można traktować funkcje jako rodzaj przepisu, w którym przechowujesz przydatne instrukcje do wykonania czegoś, co musisz zrobić w swoim programie, jak wykonanie akcji lub zwrócenie wartości. Wywołując funkcję, wykonujesz kroki zawarte w przepisie i możesz to zrobić za każdym razem, gdy wywołujesz tę funkcję, bez potrzeby przepisywania przepisu ponownie i ponownie.

Oto standardowy sposób deklarowania funkcji, a następnie jej wywoływania w JavaScript:

// function declarationfunction sayHiStranger() { return 'Hi, stranger!'}// call the functionsayHiStranger()

Możesz również napisać tę samą funkcję jako wyrażenie funkcyjne, tak jak to:

const sayHiStranger = function () { return 'Hi, stranger!'}

Funkcje strzałkowe są zawsze wyrażeniami. Oto jak mógłbyś przepisać powyższą funkcję używając notacji grubej strzałki:

const sayHiStranger = () => 'Hi, stranger'

Zaletami tego są:

  • tylko jedna linia kodu
  • brak functionsłowa kluczowego
  • brak returnsłowa kluczowego
  • brak nawiasów klamrowych {}

W JavaScript funkcje są „obywatelami pierwszej klasy”. To znaczy, można przechowywać funkcje w zmiennych, przekazywać je do innych funkcji jako argumenty i zwracać je z innych funkcji jako wartości. Możesz to wszystko zrobić za pomocą funkcji strzałek.

Przejrzyjmy różne sposoby, na jakie można pisać funkcje strzałek.

Składnia No Parens

W powyższym przykładzie, funkcja nie ma parametrów. W tym przypadku musisz dodać zestaw pustych nawiasów () przed symbolem grubej strzałki (=>). To samo dotyczy sytuacji, gdy mamy więcej niż jeden parametr:

const getNetflixSeries = (seriesName, releaseDate) => `The ${seriesName} series was released in ${releaseDate}`// call the functionconsole.log(getNetflixSeries('Bridgerton', '2020') )// output: The Bridgerton series was released in 2020

W przypadku tylko jednego parametru, możesz jednak śmiało opuścić nawiasy (nie musisz, ale możesz):

const favoriteSeries = seriesName => seriesName === "Bridgerton" ? "Let's watch it" : "Let's go out"// call the functionconsole.log(favoriteSeries("Bridgerton"))// output: "Let's watch it"

Bądź jednak ostrożny. Jeśli, na przykład, zdecydujesz się użyć domyślnego parametru, musisz zawinąć go wewnątrz nawiasów:

// with parentheses: correctconst bestNetflixSeries = (seriesName = "Bridgerton") => `${seriesName} is the best`// outputs: "Bridgerton is the best"console.log(bestNetflixSeries())// no parentheses: errorconst bestNetflixSeries = seriesName = "Bridgerton" => `${seriesName} is the best`// Uncaught SyntaxError: invalid arrow-function arguments (parentheses around the arrow-function may help)

A tylko dlatego, że możesz, nie znaczy, że powinieneś. Zmieszany z odrobiną lekkiego, dobrze pomyślanego sarkazmu, Kyle Simpson (z You Don’t Know JS fame) umieścił swoje przemyślenia na temat pomijania nawiasów w tym schemacie przepływu.

Implicit Return

Gdy masz tylko jedno wyrażenie w ciele funkcji, możesz zachować wszystko w jednej linii, usunąć nawiasy klamrowe i pozbyć się słowa kluczowego return. Właśnie zobaczyłeś, jak te zgrabne jednolinijkowce działają w powyższych przykładach. Oto jeszcze jeden przykład, tak na wszelki wypadek. Funkcja orderByLikes() robi to, co jest napisane na etykiecie: to znaczy, zwraca tablicę obiektów seriali Netflixa uporządkowanych według największej liczby polubień:

// using the JS sort() function to sort the titles in descending order // according to the number of likes (more likes at the top, fewer at the bottomconst orderByLikes = netflixSeries.sort( (a, b) => b.likes - a.likes )// call the function // output:the titles and the n. of likes in descending orderconsole.log(orderByLikes)

To jest fajne, ale zwróć uwagę na czytelność swojego kodu – zwłaszcza gdy sekwencjonujesz kilka funkcji strzałkowych za pomocą one-linerów i składni bez nawiasów, jak w tym przykładzie:

const greeter = greeting => name => `${greeting}, ${name}!`

Co tam się dzieje? Spróbuj użyć regularnej składni funkcji:

function greeter(greeting) { return function(name) { return `${greeting}, ${name}!` }} 

Teraz możesz szybko zobaczyć, jak zewnętrzna funkcja greeter ma parametr, greeting, i zwraca anonimową funkcję. Ta wewnętrzna funkcja z kolei ma parametr o nazwie name i zwraca łańcuch znaków używając wartości zarówno greeting jak i name. Oto jak można wywołać tę funkcję:

const myGreet = greeter('Good morning')console.log( myGreet('Mary') ) // output: "Good morning, Mary!" 

Watch Out for these Implicit Return Gotchas

Gdy Twoja funkcja strzałkowa zawiera więcej niż jedną instrukcję, musisz zawinąć je wszystkie w nawiasy klamrowe i użyć słowa kluczowego return. W poniższym kodzie funkcja buduje obiekt zawierający tytuł i streszczenie kilku seriali Netflixa (recenzje Netflixa pochodzą z serwisu Rotten Tomatoes) :

const seriesList = netflixSeries.map( series => { const container = {} container.title = series.name container.summary = series.summary // explicit return return container} )

Funkcja strzałki wewnątrz funkcji .map() rozwija się przez serię instrukcji, na końcu których zwraca obiekt. To sprawia, że użycie nawiasów klamrowych wokół ciała funkcji jest nieuniknione. Ponadto, ponieważ używasz nawiasów klamrowych, domyślny powrót nie jest opcją. Musisz użyć słowa kluczowego return.

Jeśli twoja funkcja strzałki zwraca dosłowny obiekt przy użyciu domyślnego zwrotu, musisz zawinąć obiekt wewnątrz nawiasów okrągłych. Nie zrobienie tego spowoduje błąd, ponieważ silnik JS błędnie przetwarza nawiasy klamrowe dosłownego obiektu jako nawiasy klamrowe funkcji. A jak już zauważyłeś powyżej, kiedy używasz nawiasów klamrowych w funkcji strzałkowej, nie możesz pominąć słowa kluczowego return.

Tę składnię zademonstrowano w krótszej wersji poprzedniego kodu:

// Uncaught SyntaxError: unexpected token: ':'const seriesList = netflixSeries.map(series => { title: series.name });// Works fineconst seriesList = netflixSeries.map(series => ({ title: series.name }));

You Can’t Name Arrow Functions

Funkcje, które nie mają identyfikatora nazwy między słowem kluczowym function a listą parametrów, są nazywane funkcjami anonimowymi. Oto jak wygląda regularne wyrażenie funkcji anonimowej:

const anonymous = function() { return 'You can\'t identify me!' }

Funkcje strzałkowe to wszystkie funkcje anonimowe:

const anonymousArrowFunc = () => 'You can\'t identify me!' 

Od ES6 zmienne i metody mogą wywnioskować nazwę funkcji anonimowej z jej pozycji syntaktycznej, używając jej właściwości name. Umożliwia to identyfikację funkcji podczas sprawdzania jej wartości lub zgłaszania błędu.

Sprawdź to, używając anonymousArrowFunc:

console.log(anonymousArrowFunc.name)// output: "anonymousArrowFunc"

Uważaj jednak, że ta wywnioskowana właściwość name istnieje tylko wtedy, gdy funkcja anonimowa jest przypisana do zmiennej, tak jak w powyższych przykładach. Jeśli używasz anonimowej funkcji jako wywołania zwrotnego, na przykład, tracisz tę użyteczną cechę. Przykładem tego jest poniższe demo, w którym anonimowa funkcja wewnątrz metody .setInterval() nie może skorzystać z właściwości name:

let counter = 5let countDown = setInterval(() => { console.log(counter) counter-- if (counter === 0) { console.log("I have no name!!") clearInterval(countDown) }}, 1000)

A to jeszcze nie wszystko. Ta wywnioskowana właściwość name nadal nie działa jako właściwy identyfikator, którego można użyć do odniesienia się do funkcji z wnętrza siebie – takich jak rekurencja, zdarzenia unbinding itp.

Wewnętrzna anonimowość funkcji strzałek doprowadziła Kyle’a Simpsona do wyrażenia swojego poglądu na funkcje strzałek w następujący sposób:

Ponieważ nie sądzę, aby anonimowe funkcje były dobrym pomysłem do częstego używania w swoich programach, nie jestem fanem używania formy => funkcji strzałki. – You Don’t Know JS

How Arrow Functions Handle the this Keyword

Najważniejszą rzeczą, jaką należy pamiętać o funkcjach strzałkowych, jest sposób, w jaki obsługują one słowo kluczowe this. W szczególności, słowo kluczowe this wewnątrz funkcji strzałki nie dostaje odbicia.

Aby zilustrować, co to oznacza, sprawdź poniższe demo:

See the Pen
JS this in arrow functions by SitePoint (@SitePoint)
on CodePen.

Tutaj znajduje się przycisk. Kliknięcie przycisku uruchamia licznik wsteczny od 5 do 1. Liczby są wyświetlane na samym przycisku:

<button class="start-btn">Start Counter</button>...const startBtn = document.querySelector(".start-btn");startBtn.addEventListener('click', function() { this.classList.add('counting') let counter = 5; const timer = setInterval(() => { this.textContent = counter counter -- if(counter < 0) { this.textContent = 'THE END!' this.classList.remove('counting') clearInterval(timer) } }, 1000) })

Zauważ, że obsługa zdarzenia wewnątrz metody .addEventListener() jest zwykłym anonimowym wyrażeniem funkcyjnym, a nie funkcją strzałkową. Dlaczego? Jeśli zalogujesz się this wewnątrz funkcji, zobaczysz, że odwołuje się ona do elementu przycisku, do którego został dołączony listener, co jest dokładnie tym, czego się oczekuje i co jest potrzebne, aby program działał zgodnie z planem:

startBtn.addEventListener('click', function() { console.log(this) ...})

A oto jak to wygląda w konsoli narzędzi deweloperskich Firefoksa:

Spróbuj jednak zastąpić zwykłą funkcję funkcją strzałki, na przykład tak:

startBtn.addEventListener('click', () => { console.log(this) ...})

Teraz this nie odwołuje się już do przycisku. Zamiast tego odwołuje się do obiektu Window:

Oznacza to, że jeśli chcesz użyć this, aby na przykład dodać klasę do przycisku po jego kliknięciu, Twój kod nie zadziała:

// change button's border's appearancethis.classList.add('counting')

Oto komunikat o błędzie w konsoli:

Dzieje się tak, ponieważ kiedy używasz funkcji strzałki, wartość słowa kluczowego this nie ulega odbiciu, ale jest dziedziczona z zakresu rodzica (nazywa się to leksykalnym zakresem). W tym konkretnym przypadku funkcja strzałki, o której mowa, jest przekazywana jako argument do metody startBtn.addEventListener(), która znajduje się w zakresie globalnym. W związku z tym, this wewnątrz uchwytu funkcji strzałki jest również związany z zakresem globalnym – czyli z obiektem Window.

Tak więc, jeśli chcesz, aby this odwoływał się do przycisku start w programie, prawidłowym podejściem jest użycie zwykłej funkcji, a nie funkcji strzałki.

Kolejną rzeczą, na którą należy zwrócić uwagę w powyższym demo, jest kod wewnątrz metody .setInterval(). Tutaj również znajdziesz funkcję anonimową, ale tym razem jest to funkcja strzałkowa. Dlaczego?

Zauważ, jaka byłaby wartość this, gdybyś użył zwykłej funkcji:

const timer = setInterval(function() { console.log(this) ...}, 1000)

Czy byłby to element button? Wcale nie. Byłby to obiekt Window!

W rzeczywistości kontekst się zmienił, ponieważ this znajduje się teraz wewnątrz funkcji niezwiązanej lub globalnej, która jest przekazywana jako argument do .setInterval(). Dlatego wartość słowa kluczowego this również uległa zmianie, ponieważ jest teraz związana z zakresem globalnym. Często stosowanym hackiem w tej sytuacji jest dołączenie innej zmiennej do przechowywania wartości słowa kluczowego this, tak aby nadal odwoływała się do oczekiwanego elementu – w tym przypadku elementu button:

const that = thisconst timer = setInterval(function() { console.log(that) ...}, 1000)

Możesz również użyć .bind(), aby rozwiązać ten problem:

const timer = setInterval(function() { console.log(this) ...}.bind(this), 1000)

Z funkcjami strzałkowymi problem znika całkowicie. Oto, jaka jest wartość this, gdy używasz funkcji strzałkowej:

const timer = setInterval( () => { console.log(this) ...}, 1000)

Tym razem konsola loguje przycisk, czyli dokładnie to, co jest potrzebne. W rzeczywistości program ma zamiar zmienić tekst przycisku, więc potrzebuje this, aby odwołać się do elementu button:

const timer = setInterval( () => { console.log(this) // the button's text displays the timer value this.textContent = counter}, 1000)

Funkcje strzałek nie mają własnego kontekstu this. Dziedziczą one wartość this od rodzica i to właśnie dzięki tej właściwości są świetnym wyborem w sytuacjach takich jak ta powyżej.

Funkcje strzałkowe nie zawsze są odpowiednim narzędziem do pracy

Funkcje strzałkowe to nie tylko nowy, wymyślny sposób pisania funkcji w JavaScript. Mają one swoje własne dziwactwa i ograniczenia, co oznacza, że są przypadki, kiedy nie chcesz używać funkcji strzałki. Przykładem tego jest obsługa kliknięcia w poprzednim demo, ale nie jest to jedyny przypadek. Przyjrzyjmy się jeszcze kilku innym.

Funkcje strzałek jako metody obiektów

Funkcje strzałek nie działają dobrze jako metody na obiektach. Oto przykład. Rozważmy ten obiekt netflixSeries, który ma kilka właściwości i kilka metod. Wywołanie console.log(netflixSeries.getLikes()) powinno wypisać wiadomość z aktualną liczbą polubień, a wywołanie console.log(netflixSeries.addLike()) powinno zwiększyć liczbę polubień o jeden, a następnie wypisać nową wartość wraz z podziękowaniem na konsoli:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes: () => `${this.title} has ${this.likes} likes`, addLike: () => { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }

Zamiast tego wywołanie metody .getLikes() zwraca „undefined has NaN likes”, a wywołanie metody .addLike() zwraca „Thank you for liking undefined, which now has NaN likes”. Tak więc this.title i this.likes nie zdołają odwołać się do właściwości obiektu odpowiednio title i likes.

Po raz kolejny problem leży w leksykalnym zakresie funkcji strzałek. this wewnątrz metody obiektu odwołuje się do zakresu rodzica, którym w tym przypadku jest obiekt Window, a nie sam rodzic – czyli nie obiekt netflixSeries.

Rozwiązaniem, oczywiście, jest użycie zwykłej funkcji:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes() { return `${this.title} has ${this.likes} likes` }, addLike() { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }// call the methods console.log(netflixSeries.getLikes())console.log(netflixSeries.addLike())// output: After Life has 5 likesThank you for liking After Life, which now has 6 likes

Arrow Functions With 3rd Party Libraries

Innym gotcha, o którym należy pamiętać, jest to, że biblioteki firm trzecich często będą wiązać wywołania metod tak, aby wartość this wskazywała na coś użytecznego.

Na przykład, wewnątrz jQuery event handler, this da ci dostęp do elementu DOM, do którego handler został powiązany:

$('body').on('click', function() { console.log(this)})// <body>

Jeśli jednak użyjemy funkcji arrow – która, jak widzieliśmy, nie ma własnego kontekstu this – otrzymamy nieoczekiwane wyniki:

$('body').on('click', () =>{ console.log(this)})// Window

Oto kolejny przykład z użyciem Vue.js:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// Hello, World!

Wewnątrz haka created, this jest związany z instancją Vue, więc wyświetlany jest komunikat „Hello, World!”.

Jeśli jednak użyjemy funkcji strzałki, this będzie wskazywać na zakres nadrzędny, który nie ma właściwości message:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// undefined

Funkcje strzałek nie mają argumentów Object

Czasami może zajść potrzeba utworzenia funkcji z nieokreśloną liczbą parametrów. Na przykład, powiedzmy, że chcesz stworzyć funkcję, która wyświetli listę twoich ulubionych seriali Netflixa uporządkowanych według preferencji. Nie wiesz jednak jeszcze, ile seriali chcesz uwzględnić. JavaScript udostępnia obiekt arguments, obiekt podobny do tablicy (nie jest to jednak pełna tablica), który przechowuje wartości przekazywane do funkcji podczas jej wywoływania.

Próbuj zaimplementować tę funkcjonalność za pomocą funkcji strzałki:

const listYourFavNetflixSeries = () => { // we need to turn the arguments into a real array // so we can use .map() const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments)}console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life')) 

Gdy wywołasz funkcję, otrzymasz następujący komunikat o błędzie: Uncaught ReferenceError: arguments is not defined. Oznacza to, że obiekt arguments nie jest dostępny wewnątrz funkcji strzałki. W rzeczywistości, zastąpienie funkcji strzałki zwykłą funkcją załatwia sprawę:

const listYourFavNetflixSeries = function() { const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments) }console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))// output: 

Tak więc, jeśli potrzebujesz obiektu arguments, nie możesz używać funkcji strzałki.

Ale co, jeśli naprawdę chcesz użyć funkcji strzałki, aby powielić tę samą funkcjonalność? Jedną rzeczą, którą możesz zrobić, jest użycie parametrów odpoczynku ES6 (...). Oto jak mógłbyś przepisać swoją funkcję:

const listYourFavNetflixSeries = (...seriesList) => { return seriesList.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) }

Wniosek

Używając funkcji strzałkowych, możesz pisać zwięzłe one-linery z implicit return i w końcu zapomnieć o starodawnych hackach, aby rozwiązać wiązanie słowa kluczowego this w JavaScript. Funkcje strzałek działają również świetnie z metodami tablicowymi, takimi jak .map(), .sort(), .forEach(), .filter() i .reduce(). Pamiętaj jednak, że funkcje strzałek nie zastępują zwykłych funkcji JS i nie mogą być używane do wszystkiego.

Jeśli masz jakiekolwiek pytania na temat funkcji strzałek, lub potrzebujesz pomocy w ich poprawnym użyciu, polecam zatrzymać się na przyjaznym forum SitePoint, gdzie jest wielu doświadczonych programistów gotowych do pomocy.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.