Een functie is een parametrisch blok code dat één keer wordt gedefinieerd en later meerdere keren wordt aangeroepen. In JavaScript wordt een functie samengesteld en beïnvloed door vele componenten:
- JavaScript-code die de body van de functie vormt
- De lijst met parameters
- De variabelen die toegankelijk zijn vanuit het lexische bereik
- De geretourneerde waarde
- De context
this
wanneer de functie wordt aangeroepen - Naam of een anonieme functie
- De variabele die het functie-object bevat
-
arguments
object (of ontbreekt in een pijlfunctie)
Dit bericht leert je zes manieren om JavaScript functies te declareren: de syntaxis, voorbeelden en veel voorkomende valkuilen. Bovendien zul je begrijpen wanneer je een bepaald functietype in bepaalde omstandigheden moet gebruiken.
1. Functiedeclaratie
Een functiedeclaratie bestaat uit
function
sleutelwoord, gevolgd door een verplichte functienaam, een lijst met parameters in een paar haakjes(para1, ..., paramN)
en een paar accolades{...}
die de body code afbakenen.
Een voorbeeld van een functiedeclaratie:
// function declarationfunction isEven(num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false
function isEven(num) {...}
is een functiedeclaratie die isEven
functie definieert, die bepaalt of een getal even is.
De functiedeclaratie creëert een variabele in het huidige bereik met de identifier die gelijk is aan de functienaam. Deze variabele bevat het functie-object.
De functie-variabele wordt naar de top van het huidige bereik getild, wat betekent dat de functie kan worden aangeroepen vóór de declaratie (zie dit hoofdstuk voor meer details).
De gemaakte functie heeft een naam, wat betekent dat de name
eigenschap van het functie-object zijn naam bevat. Dat is handig bij het bekijken van de aanroep-stack: bij debugging of het lezen van foutmeldingen.
Laten we deze eigenschappen eens in een voorbeeld bekijken:
// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name) // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}
De functiedeclaratie function hello(name) {...}
creëert een variabele hello
die naar de top van het huidige bereik wordt getakeld. hello
variabele bevat het functie object en hello.name
bevat de functie naam: 'hello'
.
1.1 Een reguliere functie
De functie-declaratie komt overeen voor gevallen waarin een reguliere functie nodig is. Regelmatig betekent dat je de functie één keer declareert en later op veel verschillende plaatsen aanroept. Dit is het basisscenario:
function sum(a, b) { return a + b;}sum(5, 6); // => 11().reduce(sum) // => 10
Omdat de functie declaratie een variabele maakt in het huidige bereik, naast reguliere functie aanroepen, is het handig voor recursie of het loskoppelen van event listeners. In tegenstelling tot functie-expressies of pijlfuncties, die geen binding maken met de functievariabele bij zijn naam.
Om bijvoorbeeld de factoriaal recursief te berekenen, moet je de functie binnenin benaderen:
function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1);}factorial(4); // => 24
Binnenin factorial()
wordt een recursieve oproep gedaan met de variabele die de functie bevat: factorial(n - 1)
.
Het is mogelijk om een functie-expressie te gebruiken en deze toe te wijzen aan een reguliere variabele, bijvoorbeeld const factorial = function(n) {...}
. Maar de functie-declaratie function factorial(n)
is compact (geen noodzaak voor const
en =
).
Een belangrijke eigenschap van de functie-declaratie is het hef-mechanisme. Het maakt het mogelijk om de functie vóór de declaratie te gebruiken in hetzelfde bereik.
Hoisting is nuttig in sommige situaties. Bijvoorbeeld, als je wilt zien hoe de functie wordt aangeroepen aan het begin van een script, zonder de functie-implementatie te lezen. De functie-implementatie kan verderop in het bestand staan, zodat je daar misschien niet eens heen hoeft te scrollen.
Meer details over het aanroepen van functie-declaraties kunt u hier lezen.
1.2 Verschil met functie-expressie
Het is gemakkelijk om de functie-declaratie en de functie-expressie te verwarren. Ze lijken erg op elkaar maar produceren functies met verschillende eigenschappen.
Een gemakkelijke regel om te onthouden: de functiedeclaratie in een verklaring begint altijd met het trefwoord function
. Anders is het een functie-uitdrukking (zie 2.).
Het volgende voorbeeld is een functiedeclaratie waarbij het statement begint met het function
sleutelwoord:
// Function declaration: starts with "function"function isNil(value) { return value == null;}
In het geval van functie-uitdrukkingen begint de JavaScript-uitdrukking niet met function
het sleutelwoord (het staat ergens in het midden van de uitingscode):
// Function expression: starts with "const"const isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()const numbers = ().filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');
1.3 Functie-declaratie in conditionals
Sommige JavaScript-omgevingen kunnen een referentiefout geven bij het aanroepen van een functie waarvan de declaratie voorkomt binnen blokken {...}
van if
for
of while
statements.
Laten we de strikte modus inschakelen en kijken wat er gebeurt als een functie wordt gedeclareerd in een conditionele:
(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();
Bij het aanroepen van ok()
, gooit JavaScript ReferenceError: ok is not defined
, omdat de functiedeclaratie zich binnen een conditioneel blok bevindt.
De functie-declaratie in conditionals is toegestaan in niet-strict modus, wat het nog verwarrender maakt.
Als algemene regel voor deze situaties geldt: wanneer een functie moet worden gemaakt door conditions – gebruik dan een functie-expressie. Laten we eens kijken hoe dat kan:
(function() { 'use strict'; let ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();
Omdat de functie een regulier object is, wijst u deze toe aan een variabele, afhankelijk van de voorwaarde. Het aanroepen van ok()
werkt prima, zonder fouten.
2. Functie-uitdrukking
Een functie-uitdrukking wordt bepaald door een
function
trefwoord, gevolgd door een optionele functienaam, een lijst met parameters in een paar haakjes(para1, ..., paramN)
en een paar accolades{ ... }
die de body code afbakenen.
Een paar voorbeelden van de functie-expressie:
const count = function(array) { // Function expression return array.length;}const methods = { numbers: , sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count(); // => 3methods.sum(); // => 14
De functie-expressie creëert een functieobject dat in verschillende situaties kan worden gebruikt:
- Toegewezen aan een variabele als een object
count = function(...) {...}
- Maak een methode op een object
sum: function() {...}
- Gebruik de functie als callback
.reduce(function(...) {...})
De functie expressie is het werkpaardje in JavaScript. Meestal heb je te maken met dit type functiedeclaratie, naast de pijlfunctie (als je de voorkeur geeft aan korte syntaxis en lexicale context).
2.1 Functie-uitdrukking met naam
Een functie is anoniem als deze geen naam heeft (name
eigenschap is een lege string ''
):
( function(variable) {return typeof variable; }).name; // => ''
Dit is een anonieme functie, waarvan de naam een lege string is.
Soms kan de functienaam worden afgeleid. Bijvoorbeeld wanneer de anonieme functie aan een variabele wordt toegewezen:
const myFunctionVar = function(variable) { return typeof variable; };myFunctionVar.name; // => 'myFunctionVar'
De naam van de anonieme functie is 'myFunctionVar'
, omdat myFunctionVar
variabele-naam wordt gebruikt om de functienaam af te leiden.
Wanneer in de expressie de naam is gespecificeerd, is dit een named function expressie. Deze heeft enkele extra eigenschappen ten opzichte van een eenvoudige functie-expressie:
- Er wordt een functie met naam gemaakt, d.w.z.
name
eigenschap bevat de functienaam - In het functiegedeelte bevat een variabele met dezelfde naam het functieobject
Laten we het bovenstaande voorbeeld gebruiken, maar stellen we een naam in de functie-uitdrukking in:
const getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number'console.log(getType.name); // => 'funName'console.log(typeof funName); // => 'undefined'
function funName(variable) {...}
is een functie-expressie met naam. De variabele funName
is toegankelijk binnen functiebereik, maar niet daarbuiten. Hoe dan ook, de eigenschap name
van het functie-object bevat de naam: funName
.
2.2 Favor named function expression
Wanneer een functie expressie const fun = function() {}
wordt toegewezen aan een variabele, leiden sommige engines de functienaam af uit deze variabele. Callbacks kunnen echter worden doorgegeven als anonieme functie-expressies, zonder opslag in variabelen: de engine kan de naam dus niet bepalen.
Het is redelijk om de voorkeur te geven aan functies met een naam en anonieme functies te vermijden om voordelen te behalen zoals:
- De foutmeldingen en aanroepstacks tonen meer gedetailleerde informatie bij gebruik van de functienamen
- Makkelijker debuggen door vermindering van het aantal anonieme stacknamen
- De functienaam zegt wat de functie doet
- Je kunt de functie binnen zijn scope benaderen voor recursieve aanroepen of het ontkoppelen van event listeners
3. Verkorte methode-definitie
De verkorte methode-definitie kan worden gebruikt in een methode-declaratie voor object-literals en ES2015-klassen. Je kunt ze definiëren met een functienaam, gevolgd door een lijst parameters in een paar haakjes
(para1, ..., paramN)
en een paar accolades{ ... }
die de body statements afbakenen.
Het volgende voorbeeld maakt gebruik van een steno methodedefinitie in een objectliteral:
const collection = { items: , add(...items) { this.items.push(...items); }, get(index) { return this.items; }};collection.add('C', 'Java', 'PHP');collection.get(1) // => 'Java'
add()
en get()
methoden in collection
object worden gedefinieerd met behulp van een korte methode-definitie. Deze methoden worden op de gebruikelijke manier aangeroepen: collection.add(...)
en collection.get(...)
.
De korte benadering van methodedefinitie heeft verschillende voordelen boven de traditionele eigenschapdefinitie met een naam, dubbele punt :
en een functie-expressie add: function(...) {...}
:
- Een kortere syntaxis is gemakkelijker te begrijpen
- Shorthand methodedefinitie creëert een benoemde functie, in tegenstelling tot een functie-expressie. Het is handig voor debugging.
De class
syntaxis vereist methodeverklaringen in een korte vorm:
class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}const sun = new Star('Sun');sun.getMessage(' is shining') // => 'Sun is shining'
3.1 Computed property names and methods
ECMAScript 2015 voegt een leuke functie toe: computed property names in object literals en classes.
De computed properties gebruiken een iets andere syntax () {...}
, dus de methode definitie ziet er zo uit:
const addMethod = 'add', getMethod = 'get';const collection = { items: , (...items) { this.items.push(...items); }, (index) { return this.items; }};collection('C', 'Java', 'PHP');collection(1) // => 'Java'
(...) {...}
en (...) {...}
zijn steno methode declaraties met gecalculeerde eigenschap namen.
4. Pijlfunctie
Een pijlfunctie wordt gedefinieerd met een paar haakjes dat de lijst met parameters bevat
(param1, param2, ..., paramN)
, gevolgd door een vette pijl=>
en een paar accolades{...}
die de body statements afbakenen.
Wanneer de pijlfunctie slechts één parameter heeft, kan het paar haakjes worden weggelaten. Wanneer de functie slechts één statement bevat, kunnen de accolades ook worden weggelaten.
Laten we eens kijken naar het basisgebruik van de pijlfunctie:
const absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10absValue(5); // => 5
absValue
is een pijlfunctie die de absolute waarde van een getal berekent.
De functie die met een vette pijl wordt gedeclareerd, heeft de volgende eigenschappen:
- De pijlfunctie creëert zijn uitvoeringscontext niet, maar neemt deze lexisch aan (in tegenstelling tot functie-expressie of functiedeclaratie, die eigen
this
creëren, afhankelijk van de aanroep) - De pijlfunctie is anoniem. De engine kan echter de naam afleiden uit de variabele die de functie bevat.
-
arguments
object is niet beschikbaar in de pijl functie (in tegenstelling tot andere declaratie types diearguments
object bieden). Je bent vrij om rest parameters te gebruiken(...params)
, dat wel.
4.1 Context-transparantie
this
trefwoord is een verwarrend aspect van JavaScript (bekijk dit artikel voor een gedetailleerde uitleg over this
).
Omdat functies een eigen uitvoeringscontext maken, is het vaak moeilijk om this
waarde te detecteren.
ECMAScript 2015 verbetert het gebruik van this
door de introductie van de arrow functie, die de context lexisch neemt (of gewoon this
uit de onmiddellijke outer scope gebruikt). Dit is leuk omdat je dan niet .bind(this)
hoeft te gebruiken of de context var self = this
hoeft op te slaan als een functie de omsluitende context nodig heeft.
Laten we eens kijken hoe this
wordt geërfd van de buitenste functie:
class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}const numbersObject = new Numbers();const addMethod = numbersObject.addNumber();addMethod(1);addMethod(5);console.log(numbersObject.array); // =>
Numbers
klasse bevat een array van getallen en biedt een methode addNumber()
om nieuwe getallen in te voegen.
Wanneer addNumber()
wordt aangeroepen zonder argumenten, wordt een afsluiting teruggegeven die het invoegen van getallen mogelijk maakt. Deze sluiting is een pijl functie die this
als numbersObject
instantie heeft, omdat de context lexicaal wordt overgenomen uit addNumbers()
methode.
Zonder de pijl functie, moet je de context handmatig aanpassen. Dat betekent dat je workarounds moet gebruiken zoals .bind()
methode:
//... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...
of de context opslaan in een gescheiden variabele var self = this
:
//... const self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...
De contexttransparantie kan worden gebruikt wanneer u this
wilt behouden zoals het is, overgenomen uit de omsluitende context.
4.2 Korte callbacks
Bij het maken van een pijl functie, zijn de haakjes paren en accolades optioneel voor een enkele parameter en een enkele body statement. Dit helpt bij het maken van zeer korte callback-functies.
Laten we een functie maken die bepaalt of een array 0
bevat:
const numbers = ;numbers.some(item => item === 0); // => true
item => item === 0
is een pijl-functie die er rechttoe rechtaan uitziet.
Merk op dat geneste korte pijl functies moeilijk te lezen zijn. De handigste manier om de kortste pijl functie vorm te gebruiken is een enkele callback (zonder nesting).
Gebruik, indien nodig, de uitgebreide syntaxis van pijl functies bij het schrijven van geneste pijl functies. Het is gewoon makkelijker te lezen.
5. Generator functie
De generator functie in JavaScript retourneert een Generator object. De syntaxis is vergelijkbaar met die van functie-expressie, functiedeclaratie of methode-declaratie, alleen is er een sterteken voor nodig *
.
De generatorfunctie kan in de volgende vormen worden gedeclareerd:
a. Functie declaratie vorm function* <name>()
:
function* indexGenerator(){ var index = 0; while(true) { yield index++; }}const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
b. Functie-uitdrukking function* ()
:
const indexGenerator = function* () { let index = 0; while(true) { yield index++; }};const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
c. Shorthand methode-definitievorm *<name>()
:
const obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}const g = obj.indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
In alle 3 gevallen retourneert de generatorfunctie het generatorobject g
. Later wordt g
gebruikt om reeksen van oplopende getallen te genereren.
6. Nog iets: new Function
In JavaScript zijn functies eersteklas objecten – een functie is een regulier object van het type function
.
De hierboven beschreven manieren van declareren creëren hetzelfde functie object type. Laten we eens een voorbeeld bekijken:
function sum1(a, b) { return a + b;}const sum2 = function(a, b) { return a + b;}const sum3 = (a, b) => a + b;console.log(typeof sum1 === 'function'); // => trueconsole.log(typeof sum2 === 'function'); // => trueconsole.log(typeof sum3 === 'function'); // => true
Het functie object type heeft een constructor: Function
.
Wanneer Function
wordt aangeroepen als constructor new Function(arg1, arg2, ..., argN, bodyString)
, wordt een nieuwe functie aangemaakt. De argumenten arg1, args2, ..., argN
die aan de constructor worden doorgegeven, worden de parameternamen voor de nieuwe functie en het laatste argument bodyString
wordt gebruikt als de body-code van de functie.
Laten we een functie maken die twee getallen optelt:
const numberA = 'numberA', numberB = 'numberB';const sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25
sumFunction
gemaakt met Function
constructor invocatie heeft parameters numberA
en numberB
en de body return numberA + numberB
.
De functies die op deze manier worden gemaakt hebben geen toegang tot de huidige scope, dus closures kunnen niet worden gemaakt. Ze worden altijd gemaakt in de globale scope.
Eén mogelijke toepassing van new Function
is een betere manier om toegang te krijgen tot het globale object in een browser of NodeJS-script:
(function() { 'use strict'; const global = new Function('return this')(); console.log(global === window); // => true console.log(this === window); // => false})();
Houd in gedachten dat functies bijna nooit moeten worden gedeclareerd met new Function()
. Omdat de body van de functie runtime wordt geëvalueerd, erft deze aanpak veel eval()
gebruiksproblemen: veiligheidsrisico’s, moeilijker debuggen, geen manier om engine-optimalisaties toe te passen, geen editor auto-complete.
7. Welke manier is uiteindelijk beter?
Er is geen winnaar of verliezer. De beslissing welk declaratietype te kiezen, hangt af van de situatie.
Er zijn echter wel enkele regels die je in veel voorkomende situaties kunt volgen.
Als de functie this
uit de omsluitende functie gebruikt, is de pijlfunctie een goede oplossing. Als de callback functie één kort statement heeft, is de arrow functie ook een goede optie, omdat het korte en lichte code maakt.
Voor een kortere syntaxis bij het declareren van methoden op object-literals, verdient de shorthand methode-declaratie de voorkeur.
new Function
manier om functies normaal te declareren moet niet worden gebruikt. Voornamelijk omdat het potentiële veiligheidsrisico’s opent, code auto-complete in editors niet toestaat en de engine optimalisaties verliest.