Finestre.  Virus.  I Quaderni.  Internet.  ufficio.  Utilità.  Autisti

Istruzioni jump e gestione delle eccezioni

Un'altra categoria di operatori del linguaggio JavaScript sono gli operatori di salto. Come suggerisce il nome, queste istruzioni fanno sì che l'interprete JavaScript passi a una posizione diversa nel codice del programma. L'istruzione break fa saltare l'interprete alla fine di un ciclo o di un'altra istruzione. L'istruzione continue fa sì che l'interprete salti il ​​resto del corpo del ciclo, torni all'inizio del ciclo e inizi una nuova iterazione. IN javascriptè possibile etichettare le istruzioni con nomi, in modo che le istruzioni break e continue possano essere esplicitamente indicate a quale ciclo oa quale altra istruzione appartengono.

L'istruzione return fa sì che l'interprete salti dalla funzione chiamata al punto in cui è stata chiamata e restituisca il valore della chiamata. L'istruzione throw solleva un'eccezione ed è progettata per funzionare insieme alle istruzioni try/catch/finally che definiscono un blocco di codice per gestire l'eccezione. Questo è un tipo piuttosto complicato di istruzioni di salto: quando si verifica un'eccezione, l'interprete salta al gestore di eccezioni di inclusione più vicino, che può essere nella stessa funzione o superiore, nello stack di ritorno della funzione chiamata.

Ciascuno di questi operatori di salto è descritto più dettagliatamente nelle seguenti sottosezioni.

Etichette di istruzioni

Qualsiasi istruzione può essere contrassegnata con un identificatore e due punti prima di essa:

identificatore: istruzione

Quando si contrassegna un'istruzione, le si assegna un nome che può quindi essere utilizzato come riferimento in qualsiasi punto del programma. Puoi contrassegnare qualsiasi istruzione, ma ha senso contrassegnare solo le istruzioni che hanno un corpo, come loop e istruzioni condizionali.

Dando un nome al ciclo, può poi essere utilizzato nelle istruzioni break e continue, all'interno del ciclo per uscire da esso, o per saltare all'inizio del ciclo, all'iterazione successiva. Le istruzioni break e continue sono le uniche istruzioni nel linguaggio JavaScript che possono contenere etichette: verranno discusse più dettagliatamente in seguito. Di seguito è riportato un esempio di un'istruzione while con un'etichetta e un'istruzione continue che utilizza tale etichetta:

Mainloop: while (token != null) ( // Codice programma omesso... continue mainloop; // Vai alla successiva iterazione del ciclo denominato )

L'identificatore utilizzato come etichetta dell'istruzione può essere qualsiasi identificatore JavaScript valido eccetto parola riservata. I nomi delle etichette sono separati dai nomi delle variabili e delle funzioni, pertanto è possibile utilizzare identificatori che corrispondono ai nomi delle variabili o delle funzioni come etichette.

Le etichette di istruzione sono definite solo all'interno delle istruzioni a cui si applicano (e, ovviamente, all'interno delle istruzioni nidificate al loro interno). Le istruzioni nidificate non possono essere etichettate con gli stessi identificatori delle istruzioni che le contengono, ma due istruzioni indipendenti possono essere etichettate con la stessa etichetta. Le istruzioni etichettate possono essere rietichettate. Cioè, qualsiasi istruzione può avere più etichette.

dichiarazione di rottura

L'istruzione break fa sì che il ciclo più interno o l'istruzione switch terminino immediatamente. Abbiamo già visto esempi di utilizzo di un'istruzione break all'interno di un'istruzione switch in precedenza. Nei cicli, viene solitamente utilizzato per uscire immediatamente dal ciclo quando, per qualche motivo, è necessario terminare l'esecuzione del ciclo.

Quando un ciclo ha una condizione di terminazione molto complessa, spesso è più facile implementare tali condizioni con un'istruzione break piuttosto che cercare di esprimerle in un singolo ciclo condizionale. L'esempio seguente tenta di trovare un elemento di matrice con un valore specifico. Il ciclo termina nel solito modo quando viene raggiunta la fine dell'array, oppure con l'istruzione break, non appena viene trovato il valore desiderato:

Var arr = ["a","b","c","d","e"], risultato; per (var i = 0; i

In JavaScript, puoi specificare il nome dell'etichetta dopo la parola chiave break (un identificatore senza i due punti):

break nome_etichetta;

Quando l'istruzione break viene utilizzata con un'etichetta, salta alla fine dell'istruzione denominata o ne termina l'esecuzione. In assenza di un'istruzione con l'etichetta specificata, un tentativo di utilizzare questa forma dell'istruzione break genera un errore di sintassi. Un'istruzione denominata non deve essere un ciclo o un'istruzione switch. L'istruzione break con etichetta può "sfuggire" a qualsiasi istruzione contenitore. Un'istruzione di inclusione può anche essere un semplice blocco di istruzioni racchiuso tra parentesi graffe al solo scopo di contrassegnarlo.

Non è possibile inserire un carattere di nuova riga tra la parola chiave break e il nome dell'etichetta. Questo perché l'interprete JavaScript inserisce automaticamente i punti e virgola mancanti: se interrompi una riga di codice tra la parola chiave break e l'etichetta che la segue, l'interprete presumerà che tu intendessi la forma semplice di questo operatore senza etichetta e aggiungerà un punto e virgola .

L'istruzione break con etichetta è richiesta solo quando si desidera interrompere l'esecuzione di un'istruzione che non è l'istruzione switch o loop di inclusione più vicina.

continua dichiarazione

L'istruzione continue è simile all'istruzione break. Tuttavia, invece di uscire dal ciclo, l'istruzione continue avvia una nuova iterazione del ciclo. La sintassi per l'istruzione continue è semplice quanto la sintassi per l'istruzione break. L'istruzione continue può essere utilizzata anche con un'etichetta.

L'istruzione continue, sia senza etichetta che con etichetta, può essere utilizzata solo all'interno del corpo di un ciclo. L'utilizzo in qualsiasi altro luogo comporta un errore di sintassi. Quando viene eseguita un'istruzione continue, l'iterazione corrente del ciclo viene interrotta e inizia quella successiva. Per tipi diversi cicli significa cose diverse:

    Nel ciclo while, l'espressione specificata all'inizio del ciclo viene nuovamente verificata e, se è vera, il corpo del ciclo viene eseguito dall'inizio.

    Il ciclo do/while salta alla fine del ciclo, dove la condizione viene verificata nuovamente prima che il ciclo venga ripetuto.

    Nel ciclo for, l'espressione di incremento viene valutata e l'espressione di test viene valutata nuovamente per determinare se deve essere eseguita l'iterazione successiva.

    In un ciclo for/in, il ciclo ricomincia, assegnando alla variabile specificata il nome della proprietà successiva.

Si noti la differenza nel comportamento dell'istruzione continue nei cicli while e for. Il ciclo while ritorna direttamente alla sua condizione, e per ciclo prima valuta l'espressione di incremento e poi ritorna alla condizione. L'esempio seguente mostra l'uso di un'istruzione continue senza etichetta per uscire dall'iterazione corrente di un ciclo per i numeri pari:

var somma = 0; // Calcola la somma dei numeri dispari da 0 a 10 per (var i = 0; i

L'istruzione continue, come break, può essere utilizzata nei cicli nidificati in una forma che include un'etichetta, nel qual caso il ciclo riavviato non è necessariamente quello che contiene immediatamente l'istruzione continue. Inoltre, come con break, non sono consentite le nuove righe tra la parola chiave continue e il nome dell'etichetta.

dichiarazione di ritorno

Una chiamata di funzione è un'espressione e, come tutte le espressioni, ha un valore. L'istruzione return all'interno delle funzioni viene utilizzata per determinare il valore restituito dalla funzione. L'istruzione return può essere inserita solo nel corpo di una funzione. La sua presenza altrove è un errore di sintassi. Quando viene eseguita un'istruzione return, la funzione restituisce il valore dell'espressione al programma chiamante. Per esempio:

Se una funzione non ha un'istruzione return, quando viene chiamata, l'interprete eseguirà le istruzioni nel corpo della funzione una per una fino a raggiungere la fine della funzione, quindi restituirà il controllo al programma che l'ha chiamata. In questo caso, l'espressione di chiamata restituirà undefined. L'istruzione return è spesso l'ultima istruzione in una funzione, ma questo è completamente facoltativo: la funzione restituirà il controllo al programma chiamante non appena viene raggiunta l'istruzione return, anche se è seguita da altre istruzioni nel corpo della funzione.

L'istruzione return può essere utilizzata anche senza un'espressione, nel qual caso interrompe semplicemente la funzione e restituisce undefined al chiamante. Per esempio:

Function myFun(arr) ( // Se l'array contiene numeri negativi, interrompe la funzione for (var i = 0; i

dichiarazione di lancio

Eccezioneè un segnale che indica il verificarsi di qualche tipo di eccezione o errore. Sollevare un'eccezione (throw)è un modo per segnalare tale errore o eccezione. Catturare un'eccezione (catch) significa gestirla, ad es. intraprendere le azioni necessarie o opportune per recuperare dall'eccezione.

In JavaScript, le eccezioni vengono lanciate quando si verifica un errore in fase di esecuzione e quando il programma lo solleva esplicitamente con l'istruzione throw. Le eccezioni vengono catturate usando le istruzioni try/catch/finally, descritte più avanti.

L'istruzione throw ha la seguente sintassi:

gettare espressione;

Il risultato di un'espressione può essere un valore di qualsiasi tipo. All'istruzione throw può essere passato un numero che rappresenta il codice di errore o una stringa contenente il testo del messaggio di errore. L'interprete JavaScript genera eccezioni utilizzando un'istanza di una classe errore una delle sue sottoclassi e puoi anche utilizzare un approccio simile. L'oggetto Error ha una proprietà nome, che definisce il tipo di errore e la proprietà Messaggio A contenente la stringa passata alla funzione di costruzione. Di seguito è riportato un esempio di una funzione che genera un oggetto Error quando viene chiamato con un argomento non valido:

// Funzione fattoriale di un numero funzione fattoriale(numero) ( // Se l'argomento di input non è un valore valido, // viene lanciata un'eccezione! if (numero 1; i *= numero, numero--); /* vuoto corpo del ciclo */ return i ; ) console.log("5! = ", factorial(5)); console.log("-3! = ", fattoriale(-3));

Quando viene generata un'eccezione, l'interprete JavaScript interrompe immediatamente la normale esecuzione del programma e passa al gestore di eccezioni più vicino. I gestori di eccezioni utilizzano l'istruzione catch del costrutto try/catch/finally, descritto nella sezione successiva.

Se il blocco di codice in cui si è verificata l'eccezione non ha un costrutto catch corrispondente, l'interprete analizza il successivo blocco esterno di codice e verifica se ad esso è associato un gestore di eccezioni. Questo continua finché non viene trovato il gestore.

Se viene generata un'eccezione in una funzione che non contiene un costrutto try/catch/finally per gestirla, l'eccezione si propaga nel codice che ha chiamato la funzione. Questo è il modo in cui le eccezioni si propagano attraverso la struttura lessicale dei metodi JavaScript nello stack di chiamate. Se non viene mai trovato un gestore di eccezioni, l'eccezione viene trattata come un errore e segnalata all'utente.

prova/prendi/finalmente costruisci

Il costrutto try/catch/finally implementa il meccanismo di gestione delle eccezioni di JavaScript. dichiarazione di prova in questo costrutto definisce semplicemente un blocco di codice in cui vengono gestite le eccezioni. Il blocco try è seguito da dichiarazione di cattura con un blocco di istruzioni da chiamare se si verifica un'eccezione in qualsiasi punto del blocco try. L'istruzione catch è seguita da un blocco Finalmente A che contiene il codice che esegue le operazioni finali ed è garantito che venga eseguito indipendentemente da ciò che accade nel blocco try.

Sia il blocco catch che il blocco finally sono facoltativi, ma almeno uno di essi deve essere presente dopo il blocco try. prova, cattura e infine i blocchi iniziano e finiscono con parentesi graffe. Questa è una parte obbligatoria della sintassi e non può essere omessa anche se c'è solo un'istruzione tra di loro.

Il seguente frammento illustra la sintassi e lo scopo del costrutto try/catch/finally:

Try ( // Normalmente, questo codice verrà eseguito senza problemi dall'inizio alla fine. // Ma a un certo punto potrebbe generare un'eccezione, // direttamente con l'istruzione throw o indirettamente, // chiamando il metodo che genera il eccezione. ) catch (ex) ( // Le istruzioni in questo blocco vengono eseguite se e solo se si verifica un'eccezione nel blocco try //. Queste istruzioni possono utilizzare una variabile locale ex che // fa riferimento all'oggetto Error o a un altro valore specificato nell'istruzione throw. // Questo blocco può gestire l'eccezione in qualche modo, o // ignorarla e fare qualcos'altro, o // rilanciare l'eccezione con un'istruzione throw. ) finally ( // Questo blocco contiene istruzioni che vengono sempre eseguiti, indipendentemente dal fatto che , // cosa sia successo nel blocco try Vengono eseguiti se il blocco try è terminato: // 1) come al solito, raggiungendo la fine del blocco // 2) a causa di break, continue o istruzioni return // 3) con l'eccezione gestita come indicato nel blocco catch precedente // 4) con un'eccezione non rilevata che continua a // propagarsi a livelli superiori)

Si noti che la parola chiave catch è seguita da un identificatore tra parentesi. Questo identificatore è simile a un parametro di funzione. Quando viene rilevata un'eccezione, questo parametro verrà impostato su un'eccezione (ad esempio, un oggetto Error). A differenza di una variabile normale, l'identificatore associato a un'istruzione catch esiste solo nel corpo del blocco catch.

Quello che segue è un esempio più realistico di un costrutto try/catch. Chiama il metodo factorial() definito nell'esempio precedente e i metodi prompt() e alert() di JavaScript lato client per organizzare l'input e l'output:

Prova ( // Chiedi all'utente un numero var n = Number(prompt("Inserisci un numero positivo", "")); // Calcola il fattoriale di un numero, supponendo // che l'input sia valido var f = factorial( n); // Stampa il risultato alert(n + "! = " + f); ) catch (ex) ( // Se i dati non sono corretti, il controllo verrà trasferito qui alert(ex); // Notifica all'utente l'errore )

Se l'utente inserisce un numero negativo, verrà visualizzato un messaggio di avviso:

Questo è un esempio di un costrutto try/catch senza un'istruzione finally. Anche se finalmente non è usato così spesso come cattura, a volte è comunque utile. L'esecuzione del blocco finally è garantita se almeno una parte del blocco try è stata eseguita, indipendentemente da come è terminato il codice nel blocco try. Questa funzionalità viene in genere utilizzata per eseguire operazioni finali dopo che il codice è stato eseguito in una continuazione try.

In una situazione normale, il controllo raggiunge la fine del blocco try e quindi salta al blocco finally, che esegue le necessarie operazioni finali. Se il controllo esce da un blocco try come risultato di un'istruzione return, continue o break, il blocco finally viene eseguito prima che il controllo venga trasferito altrove.

Se si verifica un'eccezione in un blocco try ed è presente un blocco catch appropriato per gestirla, il controllo viene prima trasferito al blocco catch e quindi al blocco finally. Se non esiste un blocco catch locale, control passa prima al blocco finally e quindi passa al blocco catch esterno più vicino in grado di gestire l'eccezione.

Se il blocco finally trasferisce il controllo stesso utilizzando un'istruzione return, continue, break o throw oppure chiamando un metodo che genera un'eccezione, il comando di trasferimento in sospeso viene annullato e ne viene eseguito uno nuovo. Ad esempio, se il blocco finally genera un'eccezione, tale eccezione sostituirà qualsiasi eccezione generata in precedenza.

Operatore ritorno termina la funzione corrente e ne restituisce il valore.

Il codice sorgente per questo esempio interattivo è archiviato in un repository GitHub. Se desideri contribuire al progetto di esempi interattivi, clona https://github.com/mdn/interactive-examples

Sintassi

return [[espressione]]; espressione L'espressione il cui valore verrà restituito. Se non specificato, viene invece restituito undefined.

Descrizione

Quando un'istruzione return viene chiamata in una funzione, la sua esecuzione si interrompe. Il valore specificato viene restituito nel punto in cui è stata chiamata la funzione. Ad esempio, la seguente funzione restituisce il valore al quadrato del suo argomento, x (dove x è un numero):

funzione quadrato(x) ( return x * x; ) var demo = quadrato(3); // il valore demo sarà 9

Se non viene specificato alcun valore restituito, viene invece restituito undefined.

Le seguenti espressioni terminano sempre l'esecuzione di una funzione:

ritorno; restituisce vero; restituire falso; restituire x; ritorno x + y / 3;

Punto e virgola automatico

funzione magic(x) ( return function calc(x) ( return x * 42 ); ) var risposta = magic(); risposta(1337); // 56154

Specifiche

Specifica Stato Un commento
ECMAScript 1a edizione (ECMA-262) Standard definizione originale
ECMAScript 5.1 (ECMA-262)
Standard
ECMAScript 2015 (6a edizione, ECMA-262)
Definizione di "Dichiarazione di reso" in questa specifica.
Standard
ECMAScript ultima bozza (ECMA-262)
Definizione di "Dichiarazione di reso" in questa specifica.
Bozza

Compatibilità browser

La tabella di compatibilità in questa pagina è generata da dati strutturati. Se desideri contribuire ai dati, dai un'occhiata al repository https://github.com/mdn/browser-compat-data e inviaci una richiesta pull per le modifiche.

Aggiorna i dati di compatibilità su GitHub

ComputerMobileserver
CromobordoFirefoxInternet Explorermusica liricasafarivisualizzazione Web AndroidChrome per AndroidFirefox per AndroidOpera per AndroidSafari su iOSSamsung InternetNode.js
ritornoCromo Supporto totale 1 bordo Supporto totale 12 Firefox Supporto totale 1 CIOÈ Supporto totale 3 musica lirica Supporto totalesafari Supporto totalevisualizzazione web android Supporto totale 1 Chrome Android Supporto totale 18 FirefoxAndroid Supporto totale 4 OperaAndroid Supporto totaleSafari iOS Supporto totaleSamsung Internet Android Supporto totale 1.0 nodejs Supporto totale

La gente pensa che l'informatica sia un'arte per geni. In realtà, è vero il contrario: solo un sacco di persone che fanno cose che stanno una sopra l'altra, come se formassero un muro di piccoli sassolini.

Donald Knut

Hai già visto chiamate di funzioni come alert . Le funzioni sono il pane quotidiano della programmazione JavaScript. L'idea di avvolgere un pezzo di un programma e chiamarlo come variabile è molto popolare. È uno strumento strutturante. grandi programmi, riducendo la ripetizione, assegnando nomi alle subroutine e isolando le subroutine l'una dall'altra.

L'uso più ovvio delle funzioni è creare un nuovo dizionario. Inventare parole per la normale prosa umana è una cattiva forma. In un linguaggio di programmazione, questo è necessario.

L'adulto medio di lingua russa conosce circa 10.000 parole. Un raro linguaggio di programmazione contiene 10.000 comandi incorporati. E il vocabolario di un linguaggio di programmazione è definito più chiaramente, quindi è meno flessibile di quello umano. Pertanto, di solito dobbiamo aggiungere le nostre parole per evitare inutili ripetizioni.

Definizione di funzione

Una definizione di funzione è una normale definizione di variabile, in cui il valore che la variabile riceve è la funzione. Ad esempio, il codice seguente definisce una variabile quadrato che fa riferimento a una funzione che calcola il quadrato di un dato numero:

var quadrato = funzione(x) ( return x * x; ); console.log(quadrato(12)); // → 144

Una funzione viene creata da un'espressione che inizia con la parola chiave function. Le funzioni hanno un insieme di parametri (in questo caso, solo x) e un corpo contenente le istruzioni da eseguire quando viene chiamata la funzione. Il corpo di una funzione è sempre racchiuso tra parentesi graffe, anche se è costituito da una singola istruzione.

Una funzione può avere diversi parametri o nessuno. Nell'esempio seguente, makeNoise non ha un elenco di parametri, mentre power ne ha due:

Var makeNoise = function() ( console.log("Cazzo!"); ); fare rumore(); // → Merda! var potenza = funzione(base, esponente) ( var risultato = 1; for (var conteggio = 0; conteggio< exponent; count++) result *= base; return result; }; console.log(power(2, 10)); // → 1024

Alcune funzioni restituiscono un valore, come power e square, altre no, come makeNoise, che ha solo un effetto collaterale. L'istruzione return definisce il valore restituito dalla funzione. Quando l'elaborazione del programma raggiunge questa istruzione, esce immediatamente dalla funzione e restituisce questo valore al punto nel codice da cui è stata chiamata la funzione. return senza un'espressione restituisce undefined .

Parametri e portata

I parametri della funzione sono proprio come le variabili, ma i loro valori iniziali vengono impostati quando viene chiamata la funzione e non nel suo codice.

Una proprietà importante delle funzioni è che le variabili create all'interno di una funzione (compresi i parametri) sono locali all'interno di quella funzione. Ciò significa che nell'esempio di potenza, la variabile risultato verrà creata ogni volta che viene chiamata la funzione e queste sue incarnazioni separate non sono in alcun modo correlate tra loro.

Questa località delle variabili si applica solo ai parametri e alle variabili create all'interno delle funzioni. Le variabili impostate al di fuori di qualsiasi funzione sono chiamate variabili globali perché sono visibili in tutto il programma. È inoltre possibile accedere a tali variabili all'interno di una funzione, a meno che non sia stata dichiarata una variabile locale con lo stesso nome.

Il codice seguente illustra questo. Definisce e chiama due funzioni che assegnano un valore a x. Il primo lo dichiara come locale, cambiando così solo la variabile locale. Il secondo non dichiara, quindi lavorare con x all'interno della funzione fa riferimento alla variabile globale x che è stata impostata all'inizio dell'esempio.

var x = "fuori"; var f1 = funzione() ( var x = "dentro f1"; ); f1(); log della console(x); // → esterno var f2 = function() ( x = "interno f2"; ); f2(); log della console(x); // → dentro f2

Questo comportamento aiuta a prevenire l'interazione accidentale tra le funzioni. Se tutte le variabili fossero utilizzate in qualsiasi punto del programma, sarebbe molto difficile assicurarsi che una variabile non venga utilizzata per scopi diversi. E se dovessi riutilizzare una variabile, ti imbatteresti in strani effetti in cui il codice di terze parti fa confusione con i valori della tua variabile. Trattando le variabili locali della funzione in modo che esistano solo all'interno della funzione, il linguaggio consente di lavorare con le funzioni come se fossero piccoli universi separati, il che consente di non preoccuparsi dell'intero codice nel suo complesso.

Ambiti nidificati

JavaScript distingue non solo tra variabili globali e locali. Le funzioni possono essere definite all'interno di funzioni, risultando in diversi livelli di località.

Ad esempio, la seguente funzione piuttosto priva di significato ne contiene altre due:

var landscape = function() ( var result = ""; var flat = function(size) ( for (var count = 0; count< size; count++) result += "_"; }; var mountain = function(size) { result += "/"; for (var count = 0; count < size; count++) result += """; result += "\\"; }; flat(3); mountain(4); flat(6); mountain(1); flat(1); return result; }; console.log(landscape()); // → ___/""""\______/"\_

Le funzioni flat e mountain vedono la variabile risultato perché sono all'interno della funzione in cui è definita. Ma non possono vedere le variabili di conteggio reciproche, perché le variabili di una funzione sono al di fuori dell'ambito dell'altra. E l'ambiente al di fuori della funzione paesaggio non vede nessuna delle variabili definite all'interno di questa funzione.

In breve, in ogni ambito locale è possibile visualizzare tutti gli ambiti che lo contengono. L'insieme di variabili disponibili all'interno di una funzione è determinato dal punto in cui questa funzione è dichiarata nel programma. Sono visibili tutte le variabili dei blocchi che circondano la definizione della funzione, comprese quelle definite al livello superiore nel programma principale. Questo approccio agli ambiti è chiamato lessicale.

Le persone che hanno studiato altri linguaggi di programmazione potrebbero pensare che qualsiasi blocco racchiuso tra parentesi graffe crei il proprio ambiente locale. Ma in JavaScript, solo le funzioni creano ambito. Puoi utilizzare blocchi autonomi:

var qualcosa = 1; ( var qualcosa = 2; // Fai qualcosa con la variabile qualcosa... ) // Esci dal blocco...

Ma qualcosa all'interno del blocco è la stessa variabile dell'esterno. Sebbene tali blocchi siano consentiti, ha senso usarli solo per istruzioni if ​​e cicli.

Se questo ti sembra strano, non lo sembra solo a te. Nella versione 1.7 di JavaScript, parola chiave let , che funziona come var, ma crea variabili che sono locali per ogni dato blocco, non solo per la funzione.

Funzioni come valori

I nomi delle funzioni sono generalmente usati come nome per una parte di un programma. Tale variabile è una volta impostata e non cambia. Quindi è facile confondere una funzione con il suo nome.

Ma queste sono due cose diverse. Una chiamata di funzione può essere utilizzata come una semplice variabile, ad esempio può essere utilizzata in qualsiasi espressione. È possibile memorizzare una chiamata di funzione in una nuova variabile, passarla come parametro a un'altra funzione e così via. Inoltre, la variabile che memorizza la chiamata alla funzione rimane una variabile ordinaria e il suo valore può essere modificato:

Var launchMissiles = function(value) ( ​​​​missileSystem. launch("please!"); ); if (safeMode) launchMissiles = function(value) (/* release */);

Nel Capitolo 5 discuteremo delle cose meravigliose che si possono fare passando chiamate di funzione ad altre funzioni.

Dichiarazione di funzione

Esiste una versione più breve dell'espressione “var square = function…”. La parola chiave function può essere utilizzata all'inizio di un'istruzione:

funzione quadrato(x) ( return x * x; )

Questa è una dichiarazione di funzione. L'istruzione definisce la variabile quadrato e le assegna la funzione data. Finora è tutto ok. C'è solo una trappola in una tale definizione.

Console.log("Il futuro dice:", future()); function future() ( return "Non abbiamo ANCORA macchine volanti."; )

Questo codice funziona anche se la funzione è dichiarata sotto il codice che la utilizza. Questo perché le dichiarazioni di funzione non fanno parte della normale esecuzione top-down dei programmi. Si "spostano" all'inizio del loro ambito e possono essere chiamati da qualsiasi codice in tale ambito. Questo a volte è conveniente perché puoi scrivere il codice nell'ordine che ha più senso senza doversi preoccupare di dover definire tutte le funzioni sopra dove vengono utilizzate.

Ma cosa succede se inseriamo una dichiarazione di funzione all'interno di un blocco condizionale o di un ciclo? Non devi farlo. Storicamente, diverse piattaforme per l'esecuzione di JavaScript hanno gestito questi casi in modo diverso e l'attuale standard del linguaggio lo vieta. Se vuoi che i tuoi programmi funzionino in modo coerente, usa le dichiarazioni di funzione solo all'interno di altre funzioni o del programma principale.

Esempio di funzione() ( funzione a() () // Normule if (qualcosa) ( funzione b() () // Ay-yy-yy! ) )

pila di chiamate
È utile dare un'occhiata più da vicino a come funziona l'ordine di esecuzione con le funzioni. Ecco un semplice programma con alcune chiamate di funzione:

Funzione saluta(chi) ( console.log("Ciao, " + chi); ) saluta("Semyon"); console.log("Pokeda");

Viene elaborato in questo modo: chiamando greet fa saltare il passaggio all'inizio della funzione. Chiama la funzione console.log incorporata, che prende il controllo, fa il suo dovere e restituisce il controllo. Quindi raggiunge la fine del saluto e ritorna al luogo da cui è stato chiamato. La riga successiva chiama di nuovo console.log.

Schematicamente, questo può essere mostrato come segue:

In alto saluta console.log saluta in alto console.log in alto

Poiché la funzione deve tornare da dove è stata chiamata, il computer deve ricordare il contesto da cui è stata chiamata la funzione. In un caso, console.log dovrebbe tornare a greet. In un altro, ritorna alla fine del programma.

Il luogo in cui il computer ricorda il contesto è chiamato stack. Ogni volta che viene chiamata la funzione, il contesto corrente viene inserito in cima allo stack. Quando la funzione ritorna, estrae il contesto superiore dallo stack e lo usa per andare avanti.

L'archiviazione in stack richiede spazio di memoria. Quando lo stack diventa troppo grande, il computer interrompe l'esecuzione e genera qualcosa come "stack overflow" o "troppa ricorsione". Il codice seguente lo dimostra: pone al computer una domanda molto complessa che porta a salti infiniti tra due funzioni. Più precisamente, sarebbero salti infiniti se il computer avesse uno stack infinito. In realtà, lo stack trabocca.

Function chicken() ( return egg(); ) function egg() ( return chicken(); ) console. log(chicken() + " came first."); // → ??

Argomenti facoltativi
Il seguente codice è completamente legale e funziona senza problemi:

Alert("Ciao", "Buonasera", "Ciao a tutti!");

Ufficialmente, la funzione accetta un argomento. Tuttavia, quando viene sfidata in questo modo, non si lamenta. Ignora il resto degli argomenti e mostra "Ciao".

JavaScript è molto indulgente riguardo al numero di argomenti passati a una funzione. Se passi troppo, quelli extra verranno ignorati. Troppo pochi: a quelli mancanti verrà assegnato il valore undefined.

Lo svantaggio di questo approccio è che è possibile - e anche probabile - passare il numero sbagliato di argomenti alla funzione, e nessuno se ne lamenterà.

Il lato positivo è che puoi creare funzioni che accettano argomenti opzionali. Ad esempio, nella prossima versione della funzione di potenza, può essere chiamata sia con due che con un argomento: in quest'ultimo caso, l'esponente sarà uguale a due e la funzione funziona come un quadrato.

Funzione potenza(base, esponente) ( if (esponente == non definito) esponente = 2; var risultato = 1; for (var conteggio = 0; conteggio< exponent; count++) result *= base; return result; } console.log(power(4)); // → 16 console.log(power(4, 3)); // → 64

Nel prossimo capitolo vedremo come il corpo di una funzione può dirci il numero esatto di argomenti che le vengono passati. Questo è utile perché consente di creare una funzione che accetta qualsiasi numero di argomenti. Ad esempio, console.log usa questa proprietà e stampa tutti gli argomenti passati:

Console.log("R", 2, "D", 2); // → R 2 RE 2

Chiusure

La possibilità di utilizzare le chiamate di funzione come variabili, unita al fatto che le variabili locali vengono ricreate ogni volta che viene chiamata una funzione, ci porta a un punto interessante. Cosa succede alle variabili locali quando una funzione fallisce?

L'esempio seguente illustra questo problema. Dichiara la funzione wrapValue, che crea una variabile locale. Quindi restituisce una funzione che legge quella variabile locale e ne restituisce il valore.

Funzione wrapValue(n) ( var localVariable = n; return function() ( return localVariable; ); ) var wrap1 = wrapValue(1); var wrap2 = wrapValue(2); log della console (wrap1 ()); // → 1 console.log(wrap2()); // → 2

Questo è valido e funziona come dovrebbe: l'accesso alla variabile rimane. Inoltre, possono esistere più istanze della stessa variabile contemporaneamente, a ulteriore conferma del fatto che le variabili locali vengono ricreate ad ogni chiamata di funzione.

Questa capacità di lavorare con un riferimento a qualche istanza di una variabile locale è chiamata chiusura. Una funzione che chiude le variabili locali è detta funzione di chiusura. Non solo ti libera dal preoccuparti delle durate variabili, ma ti consente anche di utilizzare le funzioni in modo creativo.

Con una piccola modifica, trasformiamo il nostro esempio in una funzione che moltiplica i numeri per un dato numero.

Funzione moltiplicatore(fattore) ( restituisce funzione(numero) ( restituisce numero * fattore; ); ) var due volte = moltiplicatore(2); console.log(due volte(5)); // → 10

Una variabile separata come localVariable dall'esempio wrapValue non è più necessaria. Poiché il parametro è esso stesso una variabile locale.

Ci vuole pratica per iniziare a pensare in questo modo. Una buona opzione modello mentale: immagina che la funzione congeli il codice nel suo corpo e lo avvolga in un pacchetto. Quando vedi return function(...) (...), pensala come un pannello di controllo per un pezzo di codice congelato per un uso successivo.

Nel nostro esempio, il moltiplicatore restituisce una parte di codice bloccata che memorizziamo nella variabile double. L'ultima riga richiama la funzione contenuta nella variabile, che attiva il codice memorizzato (return number * factor;). Ha ancora accesso alla variabile factor definita quando è stato chiamato il moltiplicatore e ha anche accesso all'argomento passato durante unfreeze (5) come parametro numerico.

ricorsione

Una funzione può chiamare se stessa se fa attenzione a non sovraccaricare lo stack. Una tale funzione è detta ricorsiva. Ecco un esempio di un'implementazione alternativa dell'elevazione a potenza:

Funzione potenza(base, esponente) ( if (esponente == 0) restituisce 1; altrimenti restituisce base * potenza(base, esponente - 1); ) console.log(potenza(2, 3)); // → 8

È così che i matematici definiscono l'elevamento a potenza, e forse questo descrive il concetto in modo più elegante di un ciclo. La funzione chiama se stessa molte volte con argomenti diversi per ottenere moltiplicazioni multiple.

Tuttavia, questa implementazione presenta un problema: in un normale ambiente JavaScript, è 10 volte più lenta della versione con un ciclo. Il looping è più economico che chiamare una funzione.

Il dilemma tra velocità ed eleganza è piuttosto interessante. C'è un certo divario tra la convenienza umana e la comodità della macchina. Qualsiasi programma può essere velocizzato rendendolo più grande e più complesso. Il programmatore è tenuto a trovare il giusto equilibrio.

Nel caso del primo esponenziamento, il ciclo inelegante è abbastanza semplice e diretto. Non ha senso sostituirlo con la ricorsione. Spesso, tuttavia, i programmi lavorano con concetti così complessi che si desidera ridurre l'efficienza aumentando la leggibilità.

La regola di base, che è stata ripetuta molte volte e con la quale sono completamente d'accordo: non preoccuparti delle prestazioni finché non sei sicuro che il programma rallenti. In tal caso, trova le parti che durano più a lungo e scambia l'eleganza con l'efficienza lì.

Naturalmente, non dovremmo ignorare completamente le prestazioni subito. In molti casi, come con l'elevamento a potenza, non otteniamo molta semplicità da soluzioni eleganti. A volte un programmatore esperto vede subito che un approccio semplice non sarà mai abbastanza veloce.

Ne parlo perché troppi programmatori alle prime armi si aggrappano all'efficienza anche nelle piccole cose. Il risultato è più ampio, più complesso e spesso non privo di errori. Tali programmi richiedono più tempo per essere scritti e spesso non funzionano molto più velocemente.

Ma la ricorsione non è sempre solo un'alternativa meno efficiente ai cicli. Alcuni problemi sono più facili da risolvere con la ricorsione. Molto spesso, si tratta di un attraversamento di diversi rami di alberi, ognuno dei quali può ramificarsi.

Ecco un indovinello per te: puoi ottenere un numero infinito di numeri, partendo dal numero 1, e poi sommando 5 o moltiplicando per 3. Come si scrive una funzione che, dato un numero, cerca di trovare una sequenza di tale addizioni e moltiplicazioni che portano a un dato numero? Ad esempio, il numero 13 può essere ottenuto moltiplicando prima 1 per 3 e poi aggiungendo 5 due volte. E il numero 15 è generalmente impossibile da ottenere così.

Soluzione ricorsiva:

Function findSolution(target) ( function find(start, history) ( if (start == target) return history; else if (start > target) return null; else return find(start + 5, "(" + history + " + 5)") || find(start * 3, "(" + history + " * 3)"); ) return find(1, "1"); ) console.log(findSolution(24)); // → (((1 * 3) + 5) * 3)

Questo esempio non trova necessariamente la soluzione più breve: ne soddisfa nessuna. Non mi aspetto che tu capisca immediatamente come funziona il programma. Ma andiamo in fondo a questo grande esercizio di pensiero ricorsivo.

La funzione interna find è ricorsiva. Richiede due argomenti: il numero corrente e una stringa che contiene un record di come siamo arrivati ​​a quel numero. E restituisce una stringa che mostra la nostra sequenza di passaggi o null.

Per fare ciò, la funzione esegue una delle tre azioni. Se il numero specificato è uguale all'obiettivo, la cronologia corrente è solo il modo per raggiungerlo, motivo per cui viene restituita. Se il numero dato è maggiore dell'obiettivo, non ha senso continuare la moltiplicazione e l'addizione, perché in questo modo aumenterà solo. E se non abbiamo ancora raggiunto l'obiettivo, la funzione prova entrambi i percorsi possibili partendo dal numero dato. Si evoca due volte, una con ciascuno dei modi. Se la prima chiamata non restituisce null, restituisce. In caso contrario, viene restituito il secondo.

Per capire meglio come la funzione ottiene l'effetto desiderato, diamo un'occhiata alle sue chiamate che si verificano alla ricerca di una soluzione per il numero 13.

Find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "(((1 + 5) + 5 ) + 5)") ricerca troppo grande(33, "(((1 + 5) + 5) * 3)") ricerca troppo grande(18, "((1 + 5) * 3)") ricerca troppo grande( 3, "(1 * 3)") find(8, "((1 * 3) + 5)") find(13, "(((1 * 3) + 5) + 5)") trovato!

Il rientro mostra la profondità dello stack di chiamate. La prima volta che la funzione find chiama se stessa due volte per controllare le soluzioni che iniziano con (1 + 5) e (1 * 3). La prima chiamata cerca una soluzione che inizia con (1 + 5) e utilizza la ricorsione per verificare tutte le soluzioni che restituiscono un numero inferiore o uguale al numero desiderato. Non lo trova e restituisce null. Quindi l'operatore || e passa a una chiamata di funzione che esamina l'opzione (1 * 3). Qui siamo fortunati, perché nella terza chiamata ricorsiva otteniamo 13. Questa chiamata restituisce una stringa e ciascuna delle || passa questa stringa sopra lungo il percorso, restituendo come risultato la soluzione.

Funzioni di crescita

Esistono due modi più o meno naturali per introdurre funzioni in un programma.

Innanzitutto, scrivi codice simile più volte. Questo dovrebbe essere evitato: più codice significa più spazio per gli errori e più materiale di lettura per coloro che cercano di capire il programma. Quindi prendiamo una funzionalità ricorrente, la abbiniamo bel nome e metterlo in una funzione.

Il secondo modo è scoprire la necessità di alcune nuove funzionalità degne di essere collocate in una funzione separata. Si inizia con il nome della funzione e poi si scrive il suo corpo. Puoi anche iniziare scrivendo il codice che utilizza la funzione prima che la funzione stessa sia definita.

Quanto è difficile per te nominare una funzione mostra quanto bene comprendi la sua funzionalità. Facciamo un esempio. Abbiamo bisogno di scrivere un programma che stampi due numeri, il numero di mucche e galline della fattoria, seguito dalle parole "mucche" e "galline". Devi aggiungere zeri ai numeri davanti in modo che ognuno occupi esattamente tre posizioni.

007 Mucche 011 Galline

Ovviamente, abbiamo bisogno di una funzione con due argomenti. Iniziamo a codificare.
// printFarmInventory function printFarmInventory(mucche, galline) ( var cowString = String(mucche); while (cowString.length< 3) cowString = "0" + cowString; console.log(cowString + " Коров"); var chickenString = String(chickens); while (chickenString.length < 3) chickenString = "0" + chickenString; console.log(chickenString + " Куриц"); } printFarmInventory(7, 11);

Se aggiungiamo .length a una stringa, otteniamo la sua lunghezza. Si scopre che i cicli while aggiungono zeri iniziali ai numeri finché non ottengono una stringa di 3 caratteri.

Pronto! Ma non appena stiamo per inviare il codice all'allevatore (insieme a un pesante assegno, ovviamente), lui chiama e ci dice che ha dei maiali nella fattoria, e potremmo aggiungere l'output del numero di maiali al programma?

Certo che è possibile. Ma quando iniziamo a copiare e incollare il codice di queste quattro righe, ci rendiamo conto che dobbiamo fermarci a pensare. Ci deve essere un modo migliore. Stiamo cercando di migliorare il programma:

// outputZeroPaddedWithLabel function printZeroPaddedWithLabel(numero, etichetta) ( var numberString = String(number); while (numberString.length< 3) numberString = "0" + numberString; console.log(numberString + " " + label); } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Коров"); printZeroPaddedWithLabel(chickens, "Куриц"); printZeroPaddedWithLabel(pigs, "Свиней"); } printFarmInventory(7, 11, 3);

Lavori! Ma il nome printZeroPaddedWithLabel è un po' strano. Combina tre cose - output, zero padding e un'etichetta - in un'unica funzione. Invece di inserire l'intero frammento ripetuto in una funzione, evidenziamo un concetto:

// aggiungi la funzione Zeros zeroPad(number, width) ( var string = String(number); while (string.length< width) string = "0" + string; return string; } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { console.log(zeroPad(cows, 3) + " Коров"); console.log(zeroPad(chickens, 3) + " Куриц"); console.log(zeroPad(pigs, 3) + " Свиней"); } printFarmInventory(7, 16, 3);

Una funzione con un bel nome descrittivo zeroPad rende il codice più facile da capire. E può essere utilizzato in molte situazioni, non solo nel nostro caso. Ad esempio, per visualizzare tabelle formattate con numeri.

Quanto dovrebbero essere intelligenti e versatili le funzioni? Possiamo scrivere una semplice funzione che riempie un numero con zeri fino a tre posizioni, così come una fantastica funzione generica per la formattazione dei numeri che supporta frazioni, numeri negativi, allineamento dei punti, riempimento con caratteri diversi e così via.

Una buona regola empirica è aggiungere solo le funzionalità di cui hai sicuramente bisogno. A volte si è tentati di creare framework generici per ogni piccola esigenza. Resistigli. Non finirai mai il lavoro, ma scrivi solo un mucchio di codice che nessuno userà.

Funzioni ed effetti collaterali

Le funzioni possono essere approssimativamente suddivise in quelle che vengono chiamate a causa dei loro effetti collaterali e quelle che vengono chiamate per ottenere un certo valore. Naturalmente, è anche possibile combinare queste proprietà in un'unica funzione.

Primo funzione di aiuto nell'esempio farm, printZeroPaddedWithLabel viene chiamato a causa di un effetto collaterale: stampa una stringa. Il secondo, zeroPad, a causa del valore restituito. E non è un caso che la seconda caratteristica torni utile più spesso della prima. Le funzioni che restituiscono valori sono più facili da combinare tra loro rispetto alle funzioni che creano effetti collaterali.

Una funzione pura è un tipo speciale di funzione che restituisce valore che non solo non ha effetti collaterali, ma non dipende nemmeno dagli effetti collaterali del resto del codice, ad esempio non funziona con variabili globali che possono essere modificate accidentalmente altrove. Una funzione pura, quando viene chiamata con gli stessi argomenti, restituisce lo stesso risultato (e non fa nient'altro), il che è piuttosto carino. È facile lavorare con lei. La chiamata di tale funzione può essere sostituita mentalmente dal risultato del suo lavoro, senza modificare il significato del codice. Quando vuoi testare una tale funzione, puoi semplicemente chiamarla ed essere sicuro che se funziona in questo contesto, funzionerà in qualsiasi. Le funzioni non così pure possono restituire risultati diversi a seconda di molti fattori e avere effetti collaterali difficili da testare e spiegare.

Tuttavia, non si dovrebbe essere timidi nello scrivere funzioni che non sono del tutto pulite o avviare una sacra pulizia del codice da tali funzioni. Gli effetti collaterali sono spesso utili. Non c'è modo di scrivere una versione pura della funzione console.log, e questa funzione è abbastanza utile. Alcune operazioni sono più facili da esprimere usando gli effetti collaterali.

Risultato

Questo capitolo ti ha mostrato come scrivere le tue funzioni. Quando la parola chiave function viene utilizzata come espressione, restituisce un puntatore alla chiamata di funzione. Quando viene utilizzato come istruzione, è possibile dichiarare una variabile assegnandole una chiamata di funzione.

La chiave per comprendere le funzioni sono gli ambiti locali. I parametri e le variabili dichiarate all'interno di una funzione sono locali ad essa, ricreati ogni volta che viene chiamata e non sono visibili dall'esterno. Le funzioni dichiarate all'interno di un'altra funzione hanno accesso al suo ambito.

Molto utile da condividere compiti diversi eseguito dal programma in funzioni. Non devi ripeterti, le funzioni rendono il codice più leggibile separandolo in parti semantiche, nello stesso modo in cui i capitoli e le sezioni di un libro aiutano a organizzare il testo normale.

Esercizi

Minimo
Nel capitolo precedente è stata menzionata la funzione Math.min, che restituisce il più piccolo dei suoi argomenti. Ora possiamo scrivere noi stessi una tale funzione. Scrivete una funzione min che accetti due argomenti e ne restituisca il minimo.

Console.log(min(0, 10)); // → 0 console.log(min(0, -10)); // → -10

ricorsione
Abbiamo visto che l'operatore % (resto) può essere utilizzato per determinare se un numero è pari (% 2). Ecco un altro modo per determinare:

Zero è pari.
L'unità è strana.
Qualsiasi numero N ha la stessa parità di N-2.

Scrivi una funzione ricorsiva isEven secondo queste regole. Deve accettare un numero e restituire un valore booleano.

Provalo su 50 e 75. Prova a dargli -1. Perché si comporta in questo modo? E' possibile rimediare in qualche modo?

Provalo su 50 e 75. Guarda come si comporta su -1. Perché? Puoi pensare a un modo per risolvere questo problema?

Console.log(isEven(50)); // → true console.log(isEven(75)); // → false console.log(isEven(-1)); // → ??

Contiamo i fagioli.

Il numero di carattere N di una stringa può essere ottenuto aggiungendovi .charAt(N)("stringa".charAt(5)), in modo simile all'ottenimento della lunghezza di una stringa con .length. Il valore restituito sarà una stringa di un solo carattere (ad esempio, "k"). Il primo carattere della stringa ha posizione 0, il che significa che l'ultimo carattere avrà posizione stringa.lunghezza - 1. In altre parole, una stringa di due caratteri ha lunghezza 2 e le posizioni dei caratteri saranno 0 e 1.

Scrivete una funzione countBs che prenda una stringa come argomento e restituisca il numero di caratteri "B" nella stringa.

Quindi scrivi una funzione countChar che funzioni un po' come countBs, tranne per il fatto che accetta un secondo parametro, il carattere che cercheremo nella stringa (invece di contare solo il numero di caratteri "B"). Per fare ciò, riscrivi la funzione countBs.

Se noti un errore, seleziona una parte di testo e premi Ctrl + Invio
CONDIVIDERE: