W JavaScript, funkcja nie jest „magiczną strukturą językową”, ale specjalnym rodzajem wartości.
Składnia, której używaliśmy wcześniej jest nazywana Deklaracją funkcji:
function sayHi() { alert( "Hello" );}
Istnieje inna składnia do tworzenia funkcji, która jest nazywana Wyrażeniem funkcji.
Wygląda to tak:
let sayHi = function() { alert( "Hello" );};
Tutaj funkcja jest tworzona i przypisywana do zmiennej jawnie, jak każda inna wartość. Bez względu na to, jak funkcja jest zdefiniowana, jest to po prostu wartość przechowywana w zmiennej sayHi
.
Znaczenie tych próbek kodu jest takie samo: „utwórz funkcję i umieść ją w zmiennej sayHi
„.
Możemy nawet wydrukować tę wartość używając alert
:
function sayHi() { alert( "Hello" );}alert( sayHi ); // shows the function code
Proszę zauważyć, że ostatnia linia nie uruchamia funkcji, ponieważ nie ma nawiasów po sayHi
. Istnieją języki programowania, w których każda wzmianka o nazwie funkcji powoduje jej wykonanie, ale JavaScript taki nie jest.
W JavaScript funkcja jest wartością, więc możemy się z nią obchodzić jak z wartością. Powyższy kod pokazuje jej reprezentację łańcuchową, która jest kodem źródłowym.
Oczywiście, funkcja jest specjalną wartością, w tym sensie, że możemy ją nazwać jak sayHi()
.
Ale nadal jest to wartość. Możemy więc pracować z nią tak samo jak z innymi rodzajami wartości.
Możemy skopiować funkcję do innej zmiennej:
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)
Oto, co dzieje się powyżej w szczegółach:
- Deklaracja funkcji
(1)
tworzy funkcję i umieszcza ją w zmiennej o nazwiesayHi
. - Linia
(2)
kopiuje ją do zmiennejfunc
. Zwróć uwagę jeszcze raz: posayHi
nie ma nawiasów. Gdyby były, tofunc = sayHi()
zapisywałby wynik wywołaniasayHi()
dofunc
, a nie samą funkcjęsayHi
. - Teraz funkcję można wywołać zarówno jako
sayHi()
, jak ifunc()
.
Zauważ, że mogliśmy również użyć Function Expression do zadeklarowania sayHi
, w pierwszym wierszu:
let sayHi = function() { alert( "Hello" );};let func = sayHi;// ...
Wszystko działałoby tak samo.
Możesz się zastanawiać, dlaczego Function Expression ma średnik ;
na końcu, ale Function Declaration nie:
function sayHi() { // ...}let sayHi = function() { // ...};
Odpowiedź jest prosta:
- Nie ma potrzeby stosowania
;
na końcu bloków kodu i struktur składniowych, które ich używają, takich jakif { ... }
,for { }
,function f { }
itp. - Wewnątrz instrukcji używane jest wyrażenie funkcyjne:
let sayHi = ...;
, jako wartość. To nie jest blok kodu, ale raczej przypisanie. Średnik;
jest zalecany na końcu instrukcji, bez względu na to, jaka jest wartość. Tak więc średnik tutaj nie jest związany z samym wyrażeniem funkcyjnym, po prostu kończy wypowiedź.
Funkcje zwrotne
Spójrzmy na więcej przykładów przekazywania funkcji jako wartości i używania wyrażeń funkcyjnych.
Piszemy funkcję ask(question, yes, no)
z trzema parametrami:
question
Tekst pytania yes
Funkcja do uruchomienia, jeśli odpowiedź brzmi „Tak” no
Funkcja do uruchomienia, jeśli odpowiedź brzmi „Nie”
Funkcja powinna zadać pytanie question
i, w zależności od odpowiedzi użytkownika, wywołać yes()
lub 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);
W praktyce takie funkcje są całkiem przydatne. Główna różnica między prawdziwym ask
a powyższym przykładem polega na tym, że prawdziwe funkcje wykorzystują bardziej złożone sposoby interakcji z użytkownikiem niż prosty confirm
. W przeglądarce, taka funkcja zazwyczaj rysuje ładnie wyglądające okienko z pytaniem. Ale to już inna historia.
Argumenty showOk
i showCancel
z ask
nazywane są funkcjami wywołania zwrotnego lub po prostu callbackami.
Pomysł polega na tym, że przekazujemy funkcję i oczekujemy, że zostanie ona „wywołana z powrotem” później, jeśli zajdzie taka potrzeba. W naszym przypadku, showOk
staje się callbackiem dla odpowiedzi „tak”, a showCancel
dla odpowiedzi „nie”.
Możemy użyć Function Expressions, aby napisać tę samą funkcję znacznie krócej:
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."); });
Tutaj funkcje są zadeklarowane wewnątrz wywołania ask(...)
. Nie mają one żadnej nazwy, a więc są nazywane anonimowymi. Takie funkcje nie są dostępne poza ask
(ponieważ nie są przypisane do zmiennych), ale właśnie o to nam tutaj chodzi.
Taki kod pojawia się w naszych skryptach bardzo naturalnie, jest to zgodne z duchem JavaScriptu.
Regularne wartości, takie jak łańcuchy lub liczby reprezentują dane.
Funkcja może być postrzegana jako akcja.
Możemy ją przekazać między zmiennymi i uruchomić kiedy chcemy.
Function Expression vs Function Declaration
Sformułujmy kluczowe różnice między deklaracjami funkcji a wyrażeniami.
Po pierwsze, składnia: jak je rozróżnić w kodzie.
-
Deklaracja funkcji: funkcja, zadeklarowana jako oddzielna deklaracja, w głównym strumieniu kodu.
// Function Declarationfunction sum(a, b) { return a + b;}
-
Wyrażenie funkcji: funkcja, utworzona wewnątrz wyrażenia lub wewnątrz innej konstrukcji składniowej. Tutaj funkcja jest tworzona po prawej stronie „wyrażenia przypisania”
=
:// Function Expressionlet sum = function(a, b) { return a + b;};
Subtelniejszą różnicą jest to, kiedy funkcja jest tworzona przez silnik JavaScript.
Wyrażenie funkcyjne jest tworzone, kiedy wykonanie go osiąga i jest użyteczne tylko od tego momentu.
Gdy strumień wykonania przejdzie na prawą stronę przypisania let sum = function…
– oto idziemy, funkcja jest tworzona i może być używana (przypisywana, wywoływana, itp.) od tego momentu.
Deklaracje funkcji są inne.
Deklaracja funkcji może być wywołana wcześniej niż jest zdefiniowana.
Na przykład, globalna deklaracja funkcji jest widoczna w całym skrypcie, bez względu na to, gdzie się znajduje.
To wynika z wewnętrznych algorytmów. Kiedy JavaScript przygotowuje się do uruchomienia skryptu, najpierw szuka w nim globalnych Deklaracji Funkcji i tworzy te funkcje. Możemy myśleć o tym jako o „etapie inicjalizacji”.
A po przetworzeniu wszystkich deklaracji funkcji, kod jest wykonywany. Ma on więc dostęp do tych funkcji.
Na przykład działa to tak:
sayHi("John"); // Hello, Johnfunction sayHi(name) { alert( `Hello, ${name}` );}
Deklaracja funkcji sayHi
jest tworzona, gdy JavaScript przygotowuje się do uruchomienia skryptu i jest widoczna wszędzie w nim.
…Gdyby to było wyrażenie funkcyjne, to nie działałoby:
sayHi("John"); // error!let sayHi = function(name) { // (*) no magic any more alert( `Hello, ${name}` );};
Wyrażenia funkcyjne są tworzone, gdy wykonanie do nich dotrze. Tak stałoby się tylko w linii (*)
. Za późno.
Inną specjalną cechą deklaracji funkcji jest ich zakres blokowy.
W trybie ścisłym, gdy deklaracja funkcji znajduje się wewnątrz bloku kodu, jest widoczna wszędzie wewnątrz tego bloku. Ale nie poza nim.
Na przykład, wyobraźmy sobie, że musimy zadeklarować funkcję welcome()
w zależności od zmiennej age
, którą otrzymujemy podczas runtime. A następnie planujemy użyć jej jakiś czas później.
Jeśli użyjemy Function Declaration, nie będzie ona działać zgodnie z przeznaczeniem:
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
To dlatego, że Function Declaration jest widoczna tylko wewnątrz bloku kodu, w którym rezyduje.
Oto kolejny przykład:
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
Co możemy zrobić, aby welcome
był widoczny poza if
?
Poprawnym podejściem byłoby użycie Wyrażenia funkcyjnego i przypisanie welcome
do zmiennej, która jest zadeklarowana poza if
i ma odpowiednią widoczność.
Ten kod działa zgodnie z przeznaczeniem:
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
Albo moglibyśmy uprościć go jeszcze bardziej, używając operatora znaku zapytania ?
:
let age = prompt("What is your age?", 18);let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); };welcome(); // ok now
Jako zasada kciuka, kiedy musimy zadeklarować funkcję, pierwszą do rozważenia jest składnia Function Declaration. Daje ona więcej swobody w organizacji naszego kodu, ponieważ możemy wywoływać takie funkcje zanim zostaną zadeklarowane.
Jest to również lepsze dla czytelności, ponieważ łatwiej jest szukać function f(…) {…}
w kodzie niż let f = function(…) {…};
. Deklaracje funkcji są bardziej „przyciągające wzrok”.
…Ale jeśli deklaracja funkcji z jakiegoś powodu nam nie odpowiada, lub potrzebujemy deklaracji warunkowej (właśnie widzieliśmy przykład), to należy użyć Function Expression.
Podsumowanie
- Funkcje są wartościami. Mogą być przypisane, skopiowane lub zadeklarowane w dowolnym miejscu kodu.
- Jeśli funkcja jest zadeklarowana jako oddzielna instrukcja w głównym strumieniu kodu, to jest to nazywane „Deklaracją funkcji”.
- Jeśli funkcja jest utworzona jako część wyrażenia, to jest to nazywane „Wyrażeniem funkcji”.
- Deklaracje funkcji są przetwarzane przed wykonaniem bloku kodu. Są one widoczne wszędzie w bloku.
- Wyrażenia funkcyjne są tworzone, gdy przepływ wykonania do nich dotrze.
W większości przypadków, gdy potrzebujemy zadeklarować funkcję, deklaracja funkcji jest lepsza, ponieważ jest ona widoczna przed samą deklaracją. To daje nam większą elastyczność w organizacji kodu i zazwyczaj jest bardziej czytelne.
Więc powinniśmy używać Function Expression tylko wtedy, gdy Function Declaration nie nadaje się do tego zadania. Widzieliśmy już kilka takich przykładów w tym rozdziale, a w przyszłości zobaczymy ich jeszcze więcej.