ES6 Pijl Functies: Fat and Concise Syntax in JavaScript

In dit artikel leert u alles over de pijlfunctiesyntaxis van JavaScript – inclusief enkele van de gotchas waar u op moet letten wanneer u pijlfuncties in uw code gebruikt. U krijgt veel voorbeelden te zien die illustreren hoe ze werken.

Pijlfuncties werden in JavaScript geïntroduceerd met de release van ECMAScript 2015, ook bekend als ES6. Hun beknopte syntaxis en de manier waarop ze omgaan met het trefwoord dit, behoren tot de belangrijkste kenmerken die hebben bijgedragen aan het grote succes van pijlfuncties onder ontwikkelaars.

Een pre-ES6-functie omzetten in een pijlfunctie

Je zou functies kunnen beschouwen als een soort recept waarin je nuttige instructies opslaat om iets te bereiken dat je in je programma moet doen, zoals het uitvoeren van een actie of het retourneren van een waarde. Door je functie aan te roepen, voer je de stappen uit die in je recept staan, en dat kun je elke keer doen als je die functie aanroept, zonder dat je het recept steeds opnieuw hoeft te herschrijven.

Hier is een standaardmanier om een functie te declareren en vervolgens in JavaScript aan te roepen:

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

Je kunt dezelfde functie ook schrijven als een functie-uitdrukking, zoals dit:

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

Pijlfuncties zijn altijd expressies. Hier is hoe je de functie hierboven zou kunnen herschrijven met behulp van de vette pijlnotatie:

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

De voordelen hiervan zijn:

  • maar één regel code
  • geen function sleutelwoord
  • geen return sleutelwoord
  • geen accolades {}

In JavaScript zijn functies “eersteklas burgers”. Dat wil zeggen dat je functies kunt opslaan in variabelen, ze aan andere functies kunt doorgeven als argumenten, en ze van andere functies kunt teruggeven als waarden. Je kunt dit allemaal doen met pijlfuncties.

Laten we eens kijken naar de verschillende manieren waarop je pijlfuncties kunt schrijven.

De geen-parensyntax

In het bovenstaande voorbeeld heeft de functie geen parameters. In dat geval moet u een stel lege haakjes () toevoegen vóór het symbool van de vette pijl (=>). Hetzelfde geldt als u meer dan één parameter heeft:

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

Met slechts één parameter kunt u echter doorgaan en de haakjes weglaten (het hoeft niet, maar het kan):

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

Wees echter voorzichtig. Als je bijvoorbeeld besluit om een standaard parameter te gebruiken, moet je die tussen haakjes zetten:

// 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)

En alleen omdat het kan, betekent niet dat het moet. Vermengd met een beetje luchtig, goedbedoeld sarcasme, heeft Kyle Simpson (van You Don’t Know JS faam) zijn gedachten over het weglaten van haakjes in dit stroomdiagram gezet.

Implicit Return

Wanneer je maar één expressie in je functie body hebt, kun je alles op één regel houden, de accolades verwijderen, en het return sleutelwoord weglaten. Je hebt net gezien hoe deze handige one-liners werken in de voorbeelden hierboven. Hier is nog een voorbeeld, gewoon voor de goede orde. De functie orderByLikes() doet wat er op het plaatje staat: het geeft een array van Netflix-serie-objecten terug, gerangschikt op het hoogste aantal likes:

// 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)

Dit is cool, maar let wel op de leesbaarheid van je code – vooral wanneer je een aantal pijlfuncties achter elkaar zet met behulp van oneliners en de syntaxis zonder haakjes, zoals in dit voorbeeld:

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

Wat is daar aan de hand? Probeer de normale functiesyntaxis te gebruiken:

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

Nu kunt u snel zien hoe de buitenste functie greeter een parameter heeft, greeting, en een anonieme functie retourneert. Deze binnenste functie heeft op zijn beurt een parameter, name genaamd, en retourneert een string met de waarde van zowel greeting als name. Zo kunt u de functie aanroepen:

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

Watch Out for these Implicit Return Gotchas

Wanneer uw pijlfunctie meer dan één statement bevat, moet u ze allemaal in accolades wikkelen en het return sleutelwoord gebruiken. In de code hieronder bouwt de functie een object met de titel en samenvatting van een paar Netflix-series (Netflix beoordelingen zijn afkomstig van de Rotten Tomatoes website) :

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

De pijl functie binnen de .map() functie ontwikkelt zich over een reeks van verklaringen, aan het einde waarvan het een object retourneert. Dit maakt het gebruik van accolades rond de body van de functie onvermijdelijk. En omdat je accolades gebruikt, is een impliciete return geen optie. U moet het return sleutelwoord gebruiken.

Als uw pijlfunctie een object letterlijk retourneert met behulp van de impliciete return, moet u het object wikkelen binnen ronde haakjes. Als u dat niet doet, geeft u een foutmelding, omdat de JS-engine de accolades van het object-literatuur abusievelijk parseert als accolades van de functie. En zoals je net hierboven hebt gemerkt, wanneer je accolades gebruikt in een pijl functie, kun je het return keyword niet weglaten.

Deze syntax wordt gedemonstreerd in de kortere versie van de vorige code:

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

Je kunt Pijl Functies geen naam geven

Functies die geen naamsaanduiding hebben tussen het function keyword en de parameter lijst worden anonieme functies genoemd. Hier is hoe een regelmatige anonieme functie expressie eruit ziet:

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

Arrow functies zijn allemaal anonieme functies:

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

Vanaf ES6 kunnen variabelen en methoden de naam van een anonieme functie afleiden uit zijn syntactische positie, met behulp van de name eigenschap. Dit maakt het mogelijk om de functie te identificeren bij het inspecteren van de waarde of het melden van een fout.

Zie dit aan de hand van anonymousArrowFunc:

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

Maar wees u ervan bewust dat deze afgeleide name eigenschap alleen bestaat wanneer de anonieme functie is toegewezen aan een variabele, zoals in de voorbeelden hierboven. Als je een anonieme functie gebruikt als callback, bijvoorbeeld, verlies je deze handige eigenschap. Dit wordt geïllustreerd in de onderstaande demo waar de anonieme functie in de .setInterval() methode geen gebruik kan maken van de name eigenschap:

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

En dat is nog niet alles. Deze afgeleide name eigenschap werkt nog steeds niet als een goede identifier die u kunt gebruiken om te verwijzen naar de functie van binnenuit zichzelf – zoals voor recursie, unbinding gebeurtenissen, enz.

De intrinsieke anonimiteit van pijlfuncties heeft Kyle Simpson ertoe gebracht zijn mening over pijlfuncties als volgt te verwoorden:

Omdat ik niet denk dat anonieme functies een goed idee zijn om vaak in je programma’s te gebruiken, ben ik geen fan van het gebruik van de => pijlfunctievorm. – U kent JS niet

Hoe pijlfuncties omgaan met het trefwoord dit

Het belangrijkste dat u moet onthouden over pijlfuncties is de manier waarop ze omgaan met het trefwoord this. In het bijzonder, het this keyword binnen een arrow functie wordt niet teruggekaatst.

Om te illustreren wat dit betekent, bekijk de demo hieronder:

Zie de Pen
JS dit in pijl functies door SitePoint (@SitePoint)
op CodePen.

Hier is een knop. Als u op de knop klikt, wordt een omgekeerde teller geactiveerd van 5 tot 1. De getallen worden weergegeven op de knop zelf:

<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) })

Merk op hoe de event handler in de .addEventListener() methode een gewone anonieme functie expressie is, en geen pijl functie. Waarom? Als je this in de functie logt, zul je zien dat het verwijst naar het knop-element waaraan de listener is gekoppeld, wat precies is wat wordt verwacht en wat nodig is om het programma te laten werken zoals gepland:

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

Hier ziet het eruit in de console van de Firefox-ontwikkelaarstools:

Probeer echter de gewone functie te vervangen door een pijlfunctie, zoals deze:

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

Nu verwijst this niet meer naar de knop. In plaats daarvan verwijst het naar het Window object:

Dit betekent dat, als u this wilt gebruiken om bijvoorbeeld een class aan de knop toe te voegen nadat er op is geklikt, uw code niet zal werken:

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

Hier is de foutmelding in de console:

Dit gebeurt omdat, wanneer je een pijl functie gebruikt, de waarde van het this sleutelwoord niet wordt teruggekaatst, maar het wordt geërfd van het bereik van de ouder (dit wordt lexical scoping genoemd). In dit specifieke geval, wordt de pijl functie in kwestie doorgegeven als argument aan de startBtn.addEventListener() methode, die in het globale bereik zit. Dientengevolge is de this in de pijl functie handler ook gebonden aan het globale bereik – dat wil zeggen, aan het Window object.

Dus, als u wilt dat this verwijst naar de startknop in het programma, is de juiste aanpak het gebruik van een gewone functie, niet een pijl functie.

Het volgende dat opvalt in de demo hierboven is de code in de methode .setInterval(). Ook hier vindt u een anonieme functie, maar deze keer is het een pijl-functie. Waarom?

Noteer wat de waarde van this zou zijn als u een gewone functie zou gebruiken:

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

Zou het het button element zijn? Helemaal niet. Het zou het Window object zijn!

In feite is de context veranderd, omdat this zich nu binnen een ongebonden of globale functie bevindt die als argument aan .setInterval() wordt doorgegeven. Daarom is de waarde van het this sleutelwoord ook veranderd, omdat het nu gebonden is aan het globale bereik. Een veel voorkomende hack in deze situatie is het opnemen van een andere variabele om de waarde van het this sleutelwoord op te slaan, zodat het blijft verwijzen naar het verwachte element – in dit geval, het button element:

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

U kunt ook .bind() gebruiken om het probleem op te lossen:

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

Met pijl functies, verdwijnt het probleem helemaal. Hier ziet u wat de waarde van this is als u een pijlfunctie gebruikt:

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

Deze keer logt de console de knop, en dat is precies wat nodig is. In feite gaat het programma de tekst van de knop veranderen, dus heeft het this nodig om te verwijzen naar het button element:

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

Arrow functies hebben geen eigen this context. Ze erven de waarde van this van de parent, en het is vanwege deze eigenschap dat ze een goede keuze zijn in situaties zoals hierboven.

Pijlfuncties zijn niet altijd het juiste gereedschap voor de klus

Pijlfuncties zijn niet zomaar een nieuwe manier om functies in JavaScript te schrijven. Ze hebben hun eigen eigenaardigheden en beperkingen, wat betekent dat er gevallen zijn waarin je geen pijlfunctie wilt gebruiken. De click handler in de vorige demo is daar een voorbeeld van, maar het is niet de enige. Laten we er nog een paar bekijken.

Arrow Functions as Object Methods

Arrow functies werken niet goed als methoden op objecten. Hier is een voorbeeld. Beschouw dit netflixSeries-object, dat enkele eigenschappen en een paar methoden heeft. Het aanroepen van console.log(netflixSeries.getLikes()) zou een bericht moeten afdrukken met het huidige aantal likes, en het aanroepen van console.log(netflixSeries.addLike()) zou het aantal likes met één moeten verhogen en dan de nieuwe waarde met een dankbericht op de console moeten afdrukken:

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` } }

In plaats daarvan geeft het aanroepen van de methode .getLikes() als resultaat “ongedefinieerd heeft NaN likes”, en het aanroepen van de methode .addLike() geeft als resultaat “Dank u voor het liken van ongedefinieerd, dat nu NaN likes heeft”. Dus, this.title en this.likes falen om te verwijzen naar de eigenschappen van het object title en likes respectievelijk.

Wederom ligt het probleem bij de lexicale scoping van pijl functies. De this in de methode van het object verwijst naar het bereik van de ouder, die in dit geval het Window object is, niet de ouder zelf – dat wil zeggen, niet het netflixSeries object.

De oplossing is natuurlijk om een gewone functie te gebruiken:

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

Een andere gotcha om op te letten is dat third-party bibliotheken vaak methode-aanroepen zullen binden, zodat de this waarde naar iets nuttigs wijst.

In een jQuery event handler bijvoorbeeld, geeft this u toegang tot het DOM element waaraan de handler was gebonden:

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

Maar als we een pijl functie gebruiken – die, zoals we hebben gezien, geen eigen this context heeft – krijgen we onverwachte resultaten:

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

Hier is een ander voorbeeld met behulp van Vue.js:

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

Binnen de created haak, this is gebonden aan de Vue instantie, zodat het “Hallo, Wereld!” bericht wordt weergegeven.

Als we echter een pijlfunctie gebruiken, zal this naar de bovenliggende scope wijzen, die geen message eigenschap heeft:

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

Arrow Functions Have No arguments Object

Soms kan het nodig zijn om een functie te maken met een onbepaald aantal parameters. Stel bijvoorbeeld dat je een functie wilt maken die een lijst van je favoriete Netflix-series maakt, gerangschikt naar voorkeur. Je weet echter nog niet hoeveel series je wilt opnemen. JavaScript stelt het object arguments beschikbaar, een array-achtig object (echter geen volledige array), waarin de waarden worden opgeslagen die aan de functie worden doorgegeven wanneer die wordt aangeroepen.

Probeer deze functionaliteit te implementeren met behulp van een pijl-functie:

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')) 

Wanneer u de functie aanroept, krijgt u de volgende foutmelding: Uncaught ReferenceError: arguments is not defined. Wat dit betekent is dat het arguments object niet beschikbaar is binnen pijl functies. In feite, het vervangen van de pijl functie door een gewone functie doet de truc:

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: 

Dus, als je het arguments object nodig hebt, kun je geen pijl functies gebruiken.

Maar wat als je echt een pijl functie wilt gebruiken om dezelfde functionaliteit te repliceren? Een ding dat u kunt doen is ES6 rest-parameters gebruiken (...). Hier is hoe u uw functie zou kunnen herschrijven:

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

Conclusie

Door pijl functies te gebruiken, kunt u beknopte one-liners schrijven met impliciete return en eindelijk oude hacks vergeten om de binding van het this keyword in JavaScript op te lossen. Pijlfuncties werken ook geweldig met array-methoden zoals .map(), .sort(), .forEach(), .filter(), en .reduce(). Maar vergeet niet dat pijlfuncties geen vervanging zijn voor gewone JS-functies, en dat ze niet voor alles kunnen worden gebruikt.

Als u vragen hebt over pijlfuncties, of hulp nodig hebt om ze precies goed te krijgen, raad ik u aan eens langs te gaan op de vriendelijke forums van SitePoint, waar veel goed geïnformeerde programmeurs klaar staan om te helpen.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.