Som vi allerede har set er en funktion i JavaScript en vrdi.
Alle vrdier i JavaScript har en type. Hvad type har en funktion?
I JavaScript er funktioner objekter.
En god mde at forestille sig funktioner er som kaldbare handlingsobjekter. Vi kan ikke kun kalde dem, men ogs behandle dem som objekter: tilfje/fjerne egenskaber, videregive som reference osv.
Egenskaben name
Funktionens objekt indeholder nogle brugbare egenskaber.
For eksempel er en funktions navn tilgngeligt som egenskaben name:
function sayHi() {
alert("Hej);
}
alert(sayHi.name); // sayHi
Det er lidt pudsigt, men tildeling af navne er smart. Det tildeler ogs det korrekte navn til en funktion, selv hvis den oprettes uden et navn og derefter jeblikkeligt tildeles til en variabel:
let sayHi = function() {
alert("Hej");
};
alert(sayHi.name); // sayHi (den har fet et navn!)
Det virker ogs hvis tildelingen er sket via en standardvrdi:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (virker!)
}
f();
I specifikationen kaldes denne funktion contextual name. Hvis funktionen ikke har et navn, s bliver det tildelt et fra konteksten.
Objektets metoder har ogs navne, som er deres ngle i objektet:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
Der skabes dog ikke navne ud af den bl luft. Der er situationer hvor det er umuligt at finde det korrekte navn. I sdanne tilflde er name-egenskaben tom, som her:
// funktion oprettet inde i et array
let arr = [function() {}];
alert( arr[0].name ); // <empty string>
// motoren har ingen mde at finde det korrekte navn, s den fr ikke noget
I praksis vil langt de fleste funktioner dog have et navn.
Egenskaben length
Der er en anden indbygget egenskab length som returnerer antallet af funktionsparametre, for eksempel:
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
Som vi kan se ovenfor s bliver rest parameteren ikke talt med.
Egenskaben length bruges nogle gange til introspection i funktioner der opererer p andre funktioner.
For eksempel vil koden nedenfor vil funktionen ask acceptere et question der skal sprges efter og et antal handler funktioner der kan kaldes.
S snart brugeren giver deres svar vil funktionen kalde de leverede handlers. Vi kan levere to slags handlers:
- En funktion uden argumenter, som kun kaldes nr brugeren giver et positivt svar.
- En funktion med argumenter, som kaldes i begge tilflde og returnerer et svar.
For at kalde handler p den rigtige mde undersger vi egenskaben handler.length.
Iden er at vi har en simpel syntaks uden argumenter for positive svar (mest almindeligt), men ogs er i stand til at understtte universelle handlers der kan hndtere bde positive og negative svar, hvis det er ndvendigt.:
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// for positive svar, begge handlers kaldes
// for negative svar, kun den anden
ask("Sprgsml?", () => alert('Du sagde ja'), result => alert(result));
Dette er et srligt mnster i programmering kaldet polymorphism der behandler argumenter forskelligt alt efter type i vores tilflde afhngigt af length. Metoden ses ofte brugt i JavaScript-biblioteker.
Brugerdefinerede egenskaber
Vi kan ogs tilfje vores egne egenskaber.
Her tilfjer vi egenskaben counter for at spore antallet af kald:
function sayHi() {
alert("Hej");
// lad os tlle hvor mange gange vi kaldes
sayHi.counter++;
}
sayHi.counter = 0; // startvrdi
sayHi(); // Hej
sayHi(); // Hej
alert( `Kaldt ${sayHi.counter} gang(e)` ); // Kaldt 2 gang(e)
En egenskab tildelt til en funktion som sayHi.counter = 0 definerer ikke en lokal variabel counter inde i den. Med andre ord, egenskaben counter og en variabel let counter er to forskellige ting.
Vi kan behandle en funktion som et objekt, gemme egenskaber i den, men det har ingen effekt p dens udfrelse. Lokale variable er ikke funktionsegenskaber og omvendt. Disse er to parallelle verdener.
Egenskaber i funktioner kan nogle gange erstatte closures. For eksempel kan vi omskrive counter-funktionen fra kapitlet Variable scope, closure til at bruge en funktionsegenskab i stedet for en variabel i en closure:
function makeCounter() {
// i stedet for:
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
count er nu gemt direkte i funktionen, ikke i dens ydre leksikale milj.
Er det bedre eller vrre end at bruge en closure?
Den vigtigste forskel er at hvis vrdien af count lever i en ydre variabel, s kan ekstern kode ikke tilg den kun indre funktioner kan ndre den. Og hvis den er bundet til en funktion, s er det muligt:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
S valget af implementering afhnger af vores ml.
Navngivet funktionsudtryk (Named Function Expression)
Navngivet funktionsudtryk eller NFE er et udtryk for funktionsudtryk der har et navn.
Tag for eksempel et almindeligt funktionsudtryk:
let sayHi = function(who) {
alert(`Hej, ${who}`);
};
og tilfj et navn til det:
let sayHi = function func(who) {
alert(`Hej, ${who}`);
};
Men hvad opnr vi ved det? Hvad er meningen med et ekstra func som navn?
Det frste vi skal huske er, at vi stadig har et funktionsudtryk. Tilfjelsen af navnet "func" efter function gjorde det ikke til en funktionsdeklaration, fordi det stadig er oprettet som en del af et tildelingsudtryk.
Tilfjelsen af et navn pvirker ikke noget andet.
Funktionen er stadig tilgngelig som sayHi():
let sayHi = function func(who) {
alert(`Hej, ${who}`);
};
sayHi("John"); // Hej, John
Der er to srlige ting ved navnet func som kan udnyttes:
- Det tillader funktionen at referere til sig selv internt.
- Det er ikke synligt uden for funktionen.
For eksempel, funktionen sayHi nedenfor kalder sig selv igen med "Guest" hvis ingen who er angivet:
let sayHi = function func(who) {
if (who) {
alert(`Hej, ${who}`);
} else {
func("Guest"); // brug func til at kalde dig selv igen
}
};
sayHi(); // Hej, Guest
// Men dette virker ikke:
func(); // Fejl, func er ikke defineret (ikke synlig uden for funktionen)
Men hvorfor vil vi bruge func? Hvorfor ikke bare bruge sayHi for det indlejrede kald?
Vi kan faktisk i de fleste tilflde bruge sayHi for det indlejrede kald, og det vil fungere fint:
let sayHi = function(who) {
if (who) {
alert(`Hej, ${who}`);
} else {
sayHi("Guest");
}
};
Problemet med denne kode er at sayHi kan ndres i det ydre kode. Hvis funktionen bliver tildelt til en anden variabel i stedet, vil koden begynde at give fejl:
let sayHi = function(who) {
if (who) {
alert(`Hej, ${who}`);
} else {
sayHi("Guest"); // Fejl: sayHi er ikke en funktion
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Fejl, det indlejrede sayHi kald virker ikke mere!
Dette sker fordi funktionen tager sayHi fra dens ydre leksikale milj. Der er ingen lokal sayHi, s den ydre variabel bruges. Og ved det jeblik hvor funktionen kaldes, er den ydre sayHi null.
Det valgfrie navn som vi kan tilfje til et funktionsudtryk er ment til at lse prcis disse typer af problemer.
Lad os bruge det til at fikse vores kode:
let sayHi = function func(who) {
if (who) {
alert(`Hej, ${who}`);
} else {
func("Guest"); // Nu virker det
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hej, Guest (indlejret kald virker)
Nu virker det fordi navnet "func" er funktionslokal. Det er ikke taget fra uden for (og ikke synligt der). Speficikationen garanterer at det altid vil referere til den nuvrende funktion.
Det ydre kode har stadig sin variabel sayHi eller welcome. Og func er et internt funktionsnavn, mden for funktionen at kalde sig selv p p en plidelig mde.
Det interne navn er kun tilgngeligt for funktionsudtryk, ikke for funktionserklringer. For funktionserklringer findes der ingen syntaks til at tilfje et internt navn.
Hvis vi har brug for et plideligt internt navn, er det den grund til at omskrive en funktionserklring til et navngivet funktionsudtryk (Named Function Expression).
Opsummering
Funktioner er objekter.
Vi har deres egenskaber.:
namefunktionens navn. Ofte taget fra funktionsdefinitionen, men hvis der ikke er nogen, forsger JavaScript at gtte det ud fra konteksten (f.eks. en tildeling).lengthantallet af argumenter i funktionsdefinitionen. Rest-parametre tlles ikke.
Hvis funktionen er deklareret som et funktionsudtryk (ikke i hovedkodeflowet), og den brer et navn, s kaldes det et navngivet funktionsudtryk (Named Function Expression). Navnet kan bruges inde i funktionen til at referere til sig selv, for rekursive kald eller lignende.
Desuden kan funktioner ogs bre yderligere egenskaber. Mange kendte JavaScript-biblioteker gr brug af denne funktion.
De opretter en hoved funktion og tilkobler mange hjlpe funktioner til den. For eksempel opretter biblioteket jQuery en funktion kaldet $. Biblioteket lodash opretter en funktion kaldet _ og tilfjer _.clone, _.keyBy og andre egenskaber til det (se eventuelt dokumentationen hvis du vil lre mere om dem). Faktisk gr de det for at mindske deres forurening af det globale objekt, s en enkelt bibliotek kun giver n global variabel. Det reducerer muligheden for navnekonflikter.
S en funktion kan udfre en nyttig opgave alene men ogs udvide dens muligheder ved at udnytte dens egne egenskaber.
Kommentarer
<code>-taggen, for flere linjer - omslut dem i<pre>-tag, for mere end 10 linjer - brug en sandbox (plnkr, jsbin, codepen)