I JavaScript är en funktion inte en ”magisk språkstruktur”, utan en speciell typ av värde.
Syntaxen som vi använde tidigare kallas för funktionsdeklaration:
function sayHi() { alert( "Hello" );}
Det finns en annan syntax för att skapa en funktion som kallas funktionsuttryck.
Den ser ut så här:
let sayHi = function() { alert( "Hello" );};
Här skapas funktionen och tilldelas variabeln explicit, som vilket annat värde som helst. Oavsett hur funktionen definieras är det bara ett värde som lagras i variabeln sayHi
.
Begreppet i dessa kodprov är detsamma: ”skapa en funktion och placera den i variabeln sayHi
”.
Vi kan till och med skriva ut det värdet med hjälp av alert
:
function sayHi() { alert( "Hello" );}alert( sayHi ); // shows the function code
Bemärk att den sista raden inte kör funktionen, eftersom det inte finns några parenteser efter sayHi
. Det finns programmeringsspråk där varje omnämnande av ett funktionsnamn leder till att den körs, men JavaScript är inte så.
I JavaScript är en funktion ett värde, så vi kan hantera den som ett värde. Koden ovan visar dess strängrepresentation, vilket är källkoden.
Säkerligen är en funktion ett speciellt värde, i den meningen att vi kan kalla den som sayHi()
.
Men det är fortfarande ett värde. Så vi kan arbeta med det som med andra typer av värden.
Vi kan kopiera en funktion till en annan variabel:
function sayHi() { // (1) create alert( "Hello" );}let func = sayHi; // (2) copyfunc(); // Hello // (3) run the copy (it works)!sayHi(); // Hello // this still works too (why wouldn't it)
Här är vad som händer ovan i detalj:
- Funktionsdeklarationen
(1)
skapar funktionen och lägger den i variabeln med namnetsayHi
. - Linje
(2)
kopierar den till variabelnfunc
. Observera igen: det finns inga parenteser eftersayHi
. Om det fanns det skullefunc = sayHi()
skriva in resultatet av anropetsayHi()
ifunc
, inte själva funktionensayHi
. - Nu kan funktionen anropas som både
sayHi()
ochfunc()
.
Bemärk att vi också kunde ha använt ett funktionsuttryck för att deklarera sayHi
, i den första raden:
let sayHi = function() { alert( "Hello" );};let func = sayHi;// ...
Allt skulle fungera likadant.
Du kanske undrar varför funktionsuttryck har ett semikolon ;
i slutet, men inte funktionsdeklaration:
function sayHi() { // ...}let sayHi = function() { // ...};
Svaret är enkelt:
- Det finns inget behov av
;
i slutet av kodblock och syntaxstrukturer som använder dem somif { ... }
,for { }
,function f { }
osv. - Ett funktionsuttryck används inne i uttalandet:
let sayHi = ...;
, som ett värde. Det är inte ett kodblock utan snarare en tilldelning. Semikolon;
rekommenderas i slutet av uttalanden, oavsett vad värdet är. Så semikolonet här är inte relaterat till själva funktionsuttrycket, det avslutar bara uttalandet.
Callback-funktioner
Låt oss titta på fler exempel på att överlämna funktioner som värden och använda funktionsuttryck.
Vi skriver en funktion ask(question, yes, no)
med tre parametrar:
question
Frågetextyes
Funktion som ska köras om svaret är ”Ja”no
Funktion som ska köras om svaret är ”Nej”
Funktionen ska ställa frågan question
och, beroende på användarens svar, anropa yes()
eller no()
:
function ask(question, yes, no) { if (confirm(question)) yes() else no();}function showOk() { alert( "You agreed." );}function showCancel() { alert( "You canceled the execution." );}// usage: functions showOk, showCancel are passed as arguments to askask("Do you agree?", showOk, showCancel);
I praktiken är sådana funktioner ganska användbara. Den stora skillnaden mellan en verklig ask
och exemplet ovan är att verkliga funktioner använder mer komplexa sätt att interagera med användaren än en enkel confirm
. I webbläsaren ritar en sådan funktion vanligtvis ett snyggt frågefönster. Men det är en annan historia.
Argumenten showOk
och showCancel
i ask
kallas callback-funktioner eller bara callbacks.
Tanken är att vi lämnar över en funktion och förväntar oss att den ska ”kallas tillbaka” senare om det behövs. I vårt fall blir showOk
callback för ”ja”-svaret och showCancel
för ”nej”-svaret.
Vi kan använda funktionsuttryck för att skriva samma funktion mycket kortare:
function ask(question, yes, no) { if (confirm(question)) yes() else no();}ask( "Do you agree?", function() { alert("You agreed."); }, function() { alert("You canceled the execution."); });
Här deklareras funktionerna direkt inne i ask(...)
call. De har inget namn och kallas därför anonyma. Sådana funktioner är inte åtkomliga utanför ask
(eftersom de inte tilldelas variabler), men det är precis vad vi vill ha här.
Den här koden dyker upp i våra skript på ett mycket naturligt sätt, det är i JavaScript:s anda.
Regulära värden som strängar eller siffror representerar data.
En funktion kan uppfattas som en handling.
Vi kan skicka den mellan variabler och köra när vi vill.
Funktionsuttryck vs funktionsdeklaration
Låt oss formulera de viktigaste skillnaderna mellan funktionsdeklarationer och uttryck.
Först syntaxen: hur man skiljer dem åt i koden.
-
Funktionsdeklaration: en funktion, deklarerad som ett separat uttalande, i det huvudsakliga kodflödet.
// Function Declarationfunction sum(a, b) { return a + b;}
-
Funktionsuttryck: en funktion, skapad inuti ett uttryck eller inuti en annan syntaxkonstruktion. Här skapas funktionen till höger om ”tilldelningsuttrycket”
=
:// Function Expressionlet sum = function(a, b) { return a + b;};
Den mer subtila skillnaden är när en funktion skapas av JavaScript-motorn.
Ett funktionsuttryck skapas när exekveringen når det och är användbart endast från och med den tidpunkten.
När exekveringsflödet passerar till höger om tilldelningen let sum = function…
– nu kör vi, skapas funktionen och kan användas (tilldelas, anropas etc. ) från och med nu.
Funktionsdeklarationer är annorlunda.
En funktionsdeklaration kan anropas tidigare än den är definierad.
En global funktionsdeklaration är till exempel synlig i hela skriptet, oavsett var den befinner sig.
Det beror på interna algoritmer. När JavaScript förbereder sig för att köra skriptet letar det först efter globala funktionsdeklarationer i det och skapar funktionerna. Vi kan se det som ett ”initialiseringsstadium”.
Och efter att alla funktionsdeklarationer har behandlats körs koden. Den har alltså tillgång till dessa funktioner.
Till exempel fungerar detta:
sayHi("John"); // Hello, Johnfunction sayHi(name) { alert( `Hello, ${name}` );}
Funktionsdeklarationen sayHi
skapas när JavaScript förbereder sig för att starta skriptet och är synlig överallt i det.
…Om det var ett funktionsuttryck skulle det inte fungera:
sayHi("John"); // error!let sayHi = function(name) { // (*) no magic any more alert( `Hello, ${name}` );};
Funktionsuttryck skapas när utförandet når dem. Detta skulle endast ske i raden (*)
. För sent.
En annan speciell egenskap hos funktionsdeklarationer är deras blockomfång.
I strikt läge, när en funktionsdeklaration finns inom ett kodblock, är den synlig överallt inom det blocket. Men inte utanför det.
Föreställ dig till exempel att vi behöver deklarera en funktion welcome()
beroende på variabeln age
som vi får under körning. Och sedan planerar vi att använda den någon gång senare.
Om vi använder funktionsdeklaration kommer det inte att fungera som avsett:
let age = prompt("What is your age?", 18);// conditionally declare a functionif (age < 18) { function welcome() { alert("Hello!"); }} else { function welcome() { alert("Greetings!"); }}// ...use it laterwelcome(); // Error: welcome is not defined
Det beror på att en funktionsdeklaration bara är synlig inuti det kodblock där den finns.
Här är ett annat exempel:
let age = 16; // take 16 as an exampleif (age < 18) { welcome(); // \ (runs) // | function welcome() { // | alert("Hello!"); // | Function Declaration is available } // | everywhere in the block where it's declared // | welcome(); // / (runs)} else { function welcome() { alert("Greetings!"); }}// Here we're out of curly braces,// so we can not see Function Declarations made inside of them.welcome(); // Error: welcome is not defined
Vad kan vi göra för att göra welcome
synlig utanför if
?
Det korrekta tillvägagångssättet är att använda ett funktionsuttryck och tilldela welcome
till den variabel som är deklarerad utanför if
och har rätt synlighet.
Denna kod fungerar som avsett:
let age = prompt("What is your age?", 18);let welcome;if (age < 18) { welcome = function() { alert("Hello!"); };} else { welcome = function() { alert("Greetings!"); };}welcome(); // ok now
Och vi skulle kunna förenkla det ännu mer genom att använda en frågeteckenoperator ?
:
let age = prompt("What is your age?", 18);let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); };welcome(); // ok now
En tumregel är att när vi behöver deklarera en funktion är syntaxen för funktionsdeklaration den första att överväga. Det ger större frihet i hur vi organiserar vår kod, eftersom vi kan anropa sådana funktioner innan de deklareras.
Det är också bättre för läsbarheten, eftersom det är lättare att slå upp function f(…) {…}
i koden än let f = function(…) {…};
. Funktionsdeklarationer är mer ”iögonfallande”.
…Men om en funktionsdeklaration inte passar oss av någon anledning, eller om vi behöver en villkorlig deklaration (vi har just sett ett exempel), bör funktionsuttryck användas.
Sammanfattning
- Funktioner är värden. De kan tilldelas, kopieras eller deklareras på vilken plats som helst i koden.
- Om funktionen deklareras som ett separat uttalande i huvudkodflödet kallas det för en ”funktionsdeklaration”.
- Om funktionen skapas som en del av ett uttryck kallas det för ett ”funktionsuttryck”.
- Funktionsdeklarationer behandlas innan kodblocket utförs. De är synliga överallt i blocket.
- Funktionsuttryck skapas när exekveringsflödet når dem.
I de flesta fall när vi behöver deklarera en funktion är en funktionsdeklaration att föredra, eftersom den är synlig före själva deklarationen. Det ger oss större flexibilitet i kodorganisationen och är oftast mer läsbart.
Så vi bör använda ett funktionsuttryck endast när en funktionsdeklaration inte är lämplig för uppgiften. Vi har sett ett par exempel på detta i det här kapitlet och kommer att se fler i framtiden.