Che cos'è una breve introduzione allo scoping lessicale?
Che cos'è una breve introduzione allo scoping lessicale?
Risposte:
Li capisco attraverso esempi. :)
Innanzitutto, ambito lessicale (chiamato anche ambito statico ), in sintassi simil-C:
void fun()
{
int x = 5;
void fun2()
{
printf("%d", x);
}
}
Ogni livello interno può accedere ai suoi livelli esterni.
C'è un altro modo, chiamato ambito dinamico usato dalla prima implementazione di Lisp , sempre in una sintassi simile al C:
void fun()
{
printf("%d", x);
}
void dummy1()
{
int x = 5;
fun();
}
void dummy2()
{
int x = 10;
fun();
}
Qui è fun
possibile accedere x
in dummy1
o dummy2
oppure x
in qualsiasi funzione che chiami fun
con x
dichiarato in esso.
dummy1();
stamperà 5,
dummy2();
stamperà 10.
Il primo è chiamato statico perché può essere dedotto in fase di compilazione e il secondo è chiamato dinamico perché l'ambito esterno è dinamico e dipende dalla chiamata a catena delle funzioni.
Trovo l'ambito statico più facile per gli occhi. La maggior parte delle lingue alla fine è andata così, persino Lisp (può fare entrambe le cose, giusto?). L'ambito dinamico è come passare i riferimenti di tutte le variabili alla funzione chiamata.
Come esempio del perché il compilatore non può dedurre l'ambito dinamico esterno di una funzione, si consideri il nostro ultimo esempio. Se scriviamo qualcosa del genere:
if(/* some condition */)
dummy1();
else
dummy2();
La catena di chiamate dipende da una condizione di runtime. Se è vero, la catena di chiamate è simile a:
dummy1 --> fun()
Se la condizione è falsa:
dummy2 --> fun()
L'ambito esterno di fun
entrambi i casi è il chiamante più il chiamante del chiamante e così via .
Solo per dire che il linguaggio C non consente funzioni nidificate né scoping dinamico.
JavaScript
. Pertanto penso che questo non dovrebbe essere contrassegnato come risposta accettata. L'ambito lessicale specificamente in JS è diverso
for
ciclo c'è il problema tipico. L'ambito lessicale per JavaScript è solo a livello di funzione, a meno che non venga utilizzato ES6 let
o const
.
Proviamo la definizione più breve possibile:
L'ambito lessicale definisce come i nomi delle variabili vengono risolti nelle funzioni nidificate: le funzioni interne contengono l'ambito delle funzioni padre anche se la funzione padre è tornata .
Questo è tutto quello che c'è da fare!
var scope = "I am global";
function whatismyscope(){
var scope = "I am just a local";
function func() {return scope;}
return func;
}
whatismyscope()()
Il codice sopra riportato restituirà "I am just a local". Non restituirà "I am a global". Perché la funzione func () conta dove è stato originariamente definito che rientra nell'ambito della funzione whatismyscope.
Non si preoccuperà di come viene chiamato (l'ambito globale / anche all'interno di un'altra funzione), ecco perché il valore dell'ambito globale I am global non verrà stampato.
Questo si chiama scoping lessicale in cui "le funzioni vengono eseguite utilizzando la catena dell'ambito che era in vigore al momento della loro definizione ", secondo la Guida alle definizioni JavaScript.
L'ambito lessicale è un concetto molto potente.
Spero che sia di aiuto..:)
L'ambito lessicale (statico AKA) si riferisce alla determinazione dell'ambito di una variabile basandosi esclusivamente sulla sua posizione all'interno del corpus testuale del codice. Una variabile si riferisce sempre al suo ambiente di massimo livello. È bene capirlo in relazione all'ambito dinamico.
L'ambito definisce l'area, dove sono disponibili funzioni, variabili e simili. La disponibilità di una variabile, ad esempio, è definita nel suo contesto, diciamo la funzione, il file o l'oggetto, in cui sono definiti. Di solito chiamiamo queste variabili locali.
La parte lessicale significa che è possibile derivare l'ambito dalla lettura del codice sorgente.
L'ambito lessicale è anche noto come ambito statico.
L'ambito dinamico definisce le variabili globali che possono essere richiamate o referenziate da qualsiasi luogo dopo essere state definite. A volte vengono chiamate variabili globali, anche se le variabili globali nella maggior parte dei linguaggi di programmazione hanno portata lessicale. Ciò significa che dalla lettura del codice si può derivare che la variabile è disponibile in questo contesto. Forse si deve seguire una clausola usi o include per trovare l'installazione o la definizione, ma il codice / compilatore conosce la variabile in questo posto.
Nell'ambito dinamico, invece, si cerca prima nella funzione locale, quindi si cerca nella funzione che ha chiamato la funzione locale, quindi si cerca nella funzione che ha chiamato quella funzione e così via, nello stack di chiamate. "Dinamico" si riferisce al cambiamento, in quanto lo stack di chiamate può essere diverso ogni volta che viene chiamata una determinata funzione, e quindi la funzione potrebbe colpire variabili diverse a seconda di dove viene chiamata. (vedi qui )
Per vedere un esempio interessante di ambito dinamico vedere qui .
Per ulteriori dettagli vedere qui e qui .
Alcuni esempi in Delphi / Object Pascal
Delphi ha portata lessicale.
unit Main;
uses aUnit; // makes available all variables in interface section of aUnit
interface
var aGlobal: string; // global in the scope of all units that use Main;
type
TmyClass = class
strict private aPrivateVar: Integer; // only known by objects of this class type
// lexical: within class definition,
// reserved word private
public aPublicVar: double; // known to everyboday that has access to a
// object of this class type
end;
implementation
var aLocalGlobal: string; // known to all functions following
// the definition in this unit
end.
Il Delphi più vicino all'ambito dinamico è la coppia di funzioni RegisterClass () / GetClass (). Per il suo utilizzo vedi qui .
Diciamo che il tempo in cui RegisterClass ([TmyClass]) viene chiamato per registrare una determinata classe non può essere previsto leggendo il codice (viene chiamato in un metodo click-button chiamato dall'utente), il codice che chiama GetClass ('TmyClass') otterrà un risultato o no. La chiamata a RegisterClass () non deve rientrare nell'ambito lessicale dell'unità mediante GetClass ();
Un'altra possibilità di ambito dinamico sono i metodi anonimi (chiusure) in Delphi 2009, poiché conoscono le variabili della loro funzione di chiamata. Non segue il percorso della chiamata da lì ricorsivamente e quindi non è completamente dinamico.
Adoro le risposte agnostiche completamente linguistiche di persone come @Arak. Dato che questa domanda è stata taggata con JavaScript , vorrei aggiungere alcune note molto specifiche per questa lingua.
In JavaScript le nostre scelte per l'ambito sono:
var _this = this; function callback(){ console.log(_this); }
callback.bind(this)
Vale la pena notare, penso, che JavaScript non ha realmente l'ambito dinamico . .bind
regola la this
parola chiave, e questo è vicino, ma tecnicamente non è lo stesso.
Ecco un esempio che dimostra entrambi gli approcci. Lo fai ogni volta che prendi una decisione su come estendere i callback così questo vale per promesse, gestori di eventi e altro.
Ecco cosa potresti chiamare Lexical Scoping
dei callback in JavaScript:
var downloadManager = {
initialize: function() {
var _this = this; // Set up `_this` for lexical access
$('.downloadLink').on('click', function () {
_this.startDownload();
});
},
startDownload: function(){
this.thinking = true;
// Request the file from the server and bind more callbacks for when it returns success or failure
}
//...
};
Un altro modo di ambito è utilizzare Function.prototype.bind
:
var downloadManager = {
initialize: function() {
$('.downloadLink').on('click', function () {
this.startDownload();
}.bind(this)); // Create a function object bound to `this`
}
//...
Questi metodi sono, per quanto ne so, equivalenti dal punto di vista comportamentale.
bind
non influisce sull'ambito.
Scoping lessicale: le variabili dichiarate al di fuori di una funzione sono variabili globali e sono visibili ovunque in un programma JavaScript. Le variabili dichiarate all'interno di una funzione hanno un ambito di funzione e sono visibili solo al codice che appare all'interno di quella funzione.
IBM lo definisce come:
La parte di un programma o unità di segmento in cui si applica una dichiarazione. Un identificatore dichiarato in una routine è noto all'interno di quella routine e in tutte le routine nidificate. Se una routine nidificata dichiara un articolo con lo stesso nome, l'elemento esterno non è disponibile nella routine nidificata.
Esempio 1:
function x() {
/*
Variable 'a' is only available to function 'x' and function 'y'.
In other words the area defined by 'x' is the lexical scope of
variable 'a'
*/
var a = "I am a";
function y() {
console.log( a )
}
y();
}
// outputs 'I am a'
x();
Esempio 2:
function x() {
var a = "I am a";
function y() {
/*
If a nested routine declares an item with the same name,
the outer item is not available in the nested routine.
*/
var a = 'I am inner a';
console.log( a )
}
y();
}
// outputs 'I am inner a'
x();
Ambito lessicale significa che in un gruppo nidificato di funzioni, le funzioni interne hanno accesso alle variabili e ad altre risorse del loro ambito genitore . Ciò significa che le funzioni figlio sono legate in modo lessicale al contesto di esecuzione dei loro genitori. L'ambito lessicale viene talvolta definito anche ambito statico .
function grandfather() {
var name = 'Hammad';
// 'likes' is not accessible here
function parent() {
// 'name' is accessible here
// 'likes' is not accessible here
function child() {
// Innermost level of the scope chain
// 'name' is also accessible here
var likes = 'Coding';
}
}
}
La cosa che noterai dell'ambito lessicale è che funziona in avanti, il che significa che il nome è accessibile dai contesti di esecuzione dei suoi figli. Ma non funziona a ritroso con i suoi genitori, il che significa che i likes
suoi genitori non possono accedere alla variabile .
Questo ci dice anche che le variabili che hanno lo stesso nome in contesti di esecuzione diversi hanno la precedenza dall'alto verso il basso dello stack di esecuzione. Una variabile, che ha un nome simile a un'altra variabile, nella funzione più interna (contesto più in alto dello stack di esecuzione) avrà una precedenza più alta.
Si noti che questo è preso da qui .
In un linguaggio semplice, l'ambito lessicale è una variabile definita all'esterno dell'ambito o l'ambito superiore è automaticamente disponibile all'interno dell'ambito, il che significa che non è necessario passarlo lì.
Esempio:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
// Output: JavaScript
bind
. Con loro, bind
non è più necessario. Per ulteriori informazioni su questa modifica di controllo stackoverflow.com/a/34361380/11127383
Manca una parte importante della conversazione relativa allo scoping lessicale e dinamico : una chiara spiegazione della durata della variabile con ambito o quando è possibile accedere alla variabile.
Lo scoping dinamico corrisponde solo molto vagamente allo scoping "globale" nel modo in cui tradizionalmente ci pensiamo (il motivo per cui ho sollevato il confronto tra i due è che è già stato menzionato - e non mi piace particolarmente il link spiegazione dell'articolo ); probabilmente è meglio non fare il confronto tra globale e dinamico - sebbene presumibilmente, secondo l'articolo collegato, "... [è] utile come sostituto di variabili con portata globale".
Quindi, in parole povere, qual è l'importante distinzione tra i due meccanismi di scoping?
L'ambito lessicale è stato definito molto bene in tutte le risposte di cui sopra: sono disponibili variabili o con ambito lessicale a livello locale della funzione in cui è stata definita.
Tuttavia, poiché non è al centro dell'OP, l'ambito dinamico non ha ricevuto molta attenzione e l'attenzione che ha ricevuto significa che probabilmente ha bisogno di un po 'di più (non è una critica ad altre risposte, ma piuttosto un "oh, quella risposta ci fece desiderare che ci fosse un po 'di più "). Quindi, ecco un po 'di più:
Scoping dinamico significa che una variabile è accessibile al programma più grande durante la durata della chiamata della funzione o, mentre la funzione è in esecuzione. Davvero, Wikipedia in realtà fa un buon lavoro con la spiegazione della differenza tra i due. Per non offuscarlo, ecco il testo che descrive lo scoping dinamico:
... [I] n scoping dinamico (o scope dinamico), se l'ambito di un nome di variabile è una determinata funzione, allora il suo ambito è il periodo di tempo durante il quale viene eseguita la funzione: mentre la funzione è in esecuzione, esiste il nome della variabile , ed è associato alla sua variabile, ma dopo che la funzione ritorna, il nome della variabile non esiste.
Ambito lessicale significa che una funzione cerca le variabili nel contesto in cui è stata definita e non nell'ambito immediatamente circostante.
Guarda come funziona l'ambito lessicale in Lisp se vuoi maggiori dettagli. La risposta selezionata da Kyle Cronin nelle variabili dinamiche e lessicali in Common Lisp è molto più chiara delle risposte qui.
Per coincidenza, l'ho imparato solo in una classe Lisp, e succede anche in JavaScript.
Ho eseguito questo codice nella console di Chrome.
// JavaScript Equivalent Lisp
var x = 5; //(setf x 5)
console.debug(x); //(print x)
function print_x(){ //(defun print-x ()
console.debug(x); // (print x)
} //)
(function(){ //(let
var x = 10; // ((x 10))
console.debug(x); // (print x)
print_x(); // (print-x)
})(); //)
Produzione:
5
10
5
Un ambito lessicale in JavaScript significa che una variabile definita all'esterno di una funzione può essere accessibile all'interno di un'altra funzione definita dopo la dichiarazione della variabile. Ma non è vero il contrario; le variabili definite all'interno di una funzione non saranno accessibili al di fuori di tale funzione.
Questo concetto è ampiamente utilizzato nelle chiusure in JavaScript.
Diciamo che abbiamo il seguente codice.
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
Ora, quando chiami add () -> questo stamperà 3.
Pertanto, la funzione add () sta accedendo alla variabile globale x
definita prima dell'aggiunta della funzione metodo. Questo viene chiamato a causa dell'ambito lessicale in JavaScript.
add()
funzione fosse chiamata immediatamente dopo lo snippet di codice specificato, verrebbe anche stampata 3. L'ambito lessicale non significa semplicemente che una funzione può accedere alle variabili globali al di fuori del contesto locale. Quindi il codice di esempio in realtà non aiuta a mostrare cosa significa scoping lessicale. Mostrare l'ambito lessicale nel codice ha davvero bisogno di un contro esempio o almeno una spiegazione di altre possibili interpretazioni del codice.
L'ambito lessicale si riferisce al lessico degli identificatori (ad es. Variabili, funzioni, ecc.) Visibili dalla posizione corrente nello stack di esecuzione.
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo
e bar
sono sempre nel lessico degli identificatori disponibili perché sono globali.
Quando function1
viene eseguito, si ha accesso a un lessico di foo2
, bar2
, foo
e bar
.
Quando function2
viene eseguito, si ha accesso a un lessico di foo3
, bar3
, foo2
, bar2
, foo
, e bar
.
Il motivo per cui le funzioni globali e / o esterne non hanno accesso agli identificatori di funzioni interne è perché l'esecuzione di quella funzione non è ancora avvenuta e pertanto nessuno dei suoi identificatori è stato assegnato alla memoria. Inoltre, una volta eseguito quel contesto interno, viene rimosso dallo stack di esecuzione, il che significa che tutti i suoi identificatori sono stati raccolti in modo inutile e non sono più disponibili.
Infine, è per questo che un contesto di esecuzione nidificata può SEMPRE accedere al suo contesto di esecuzione degli antenati e quindi perché ha accesso a un più ampio lessico di identificatori.
Vedere:
Un ringraziamento speciale a @ robr3rd per aiutare a semplificare la definizione di cui sopra.
Ecco un'angolazione diversa su questa domanda che possiamo ottenere facendo un passo indietro e osservando il ruolo dello scoping nel più ampio quadro di interpretazione (eseguendo un programma). In altre parole, immagina di creare un interprete (o un compilatore) per una lingua e di essere responsabile del calcolo dell'output, dato un programma e alcuni input ad esso.
L'interpretazione implica tenere traccia di tre cose:
Stato: ovvero variabili e posizioni di memoria di riferimento nell'heap e nello stack.
Operazioni su quello stato - vale a dire, ogni riga di codice nel programma
L' ambiente in cui viene eseguita una determinata operazione , ovvero la proiezione di stato su un'operazione.
Un interprete inizia dalla prima riga di codice in un programma, calcola il suo ambiente, esegue la linea in quell'ambiente e acquisisce il suo effetto sullo stato del programma. Segue quindi il flusso di controllo del programma per eseguire la riga di codice successiva e ripete il processo fino al termine del programma.
Il modo in cui si calcola l'ambiente per qualsiasi operazione è attraverso un insieme formale di regole definite dal linguaggio di programmazione. Il termine "associazione" viene spesso utilizzato per descrivere la mappatura dello stato generale del programma su un valore nell'ambiente. Si noti che per "stato complessivo" non intendiamo stato globale, ma piuttosto la somma totale di ogni definizione raggiungibile, in qualsiasi momento dell'esecuzione).
Questo è il framework in cui è definito il problema di scoping. Ora alla prossima parte di quali sono le nostre opzioni.
Questo è l'essenza dell'ambito dinamico , in cui l'ambiente in cui viene eseguito qualsiasi codice è legato allo stato del programma come definito dal suo contesto di esecuzione.
In altre parole, con ambito lessicale l'ambiente che ogni codice vede è destinato a dichiararsi associato a un ambito definito esplicitamente nella lingua, come un blocco o una funzione.
Antica domanda, ma ecco la mia opinione su di essa.
L'ambito lessicale (statico) si riferisce all'ambito di una variabile nel codice sorgente .
In un linguaggio come JavaScript, in cui le funzioni possono essere passate in giro e associate e ricollegate a oggetti vari, si potrebbe pensare che tale ambito dipenda da chi sta chiamando la funzione in quel momento, ma non è così. Cambiare l'ambito in questo modo sarebbe un ambito dinamico e JavaScript non lo fa, tranne forse con ilthis
riferimento all'oggetto.
Per illustrare il punto:
var a='apple';
function doit() {
var a='aardvark';
return function() {
alert(a);
}
}
var test=doit();
test();
Nell'esempio, la variabile a
è definita a livello globale, ma ombreggiata nella doit()
funzione. Questa funzione restituisce un'altra funzione che, come vedi, si basa sua
variabile al di fuori del proprio ambito.
Se lo esegui, scoprirai che il valore utilizzato è aardvark
, non apple
quale, sebbene rientri nell'ambito ditest()
funzione, non rientra nell'ambito lessicale della funzione originale. Cioè, l'ambito utilizzato è l'ambito come appare nel codice sorgente, non l'ambito in cui viene effettivamente utilizzata la funzione.
Questo fatto può avere conseguenze fastidiose. Ad esempio, potresti decidere che è più semplice organizzare le tue funzioni separatamente e quindi usarle quando arriva il momento, come in un gestore di eventi:
var a='apple',b='banana';
function init() {
var a='aardvark',b='bandicoot';
document.querySelector('button#a').onclick=function(event) {
alert(a);
}
document.querySelector('button#b').onclick=doB;
}
function doB(event) {
alert(b);
}
init();
<button id="a">A</button>
<button id="b">B</button>
Questo esempio di codice fa uno di ciascuno. Si può vedere che a causa dell'ambito lessicale, il pulsante A
utilizza la variabile interna, mentre il pulsanteB
no. Potresti finire per annidare le funzioni più di quanto ti piacerebbe.
A proposito, in entrambi gli esempi, noterai anche che le variabili interne con ambito lessicale persistono anche se la funzione di contenimento ha seguito il suo corso. Questo si chiama chiusura e si riferisce all'accesso di una funzione nidificata alle variabili esterne, anche se la funzione esterna è terminata. JavaScript deve essere abbastanza intelligente da determinare se tali variabili non sono più necessarie e, in caso contrario, è possibile raccoglierle.
Normalmente imparo con l'esempio, ed ecco qualcosa:
const lives = 0;
function catCircus () {
this.lives = 1;
const lives = 2;
const cat1 = {
lives: 5,
jumps: () => {
console.log(this.lives);
}
};
cat1.jumps(); // 1
console.log(cat1); // { lives: 5, jumps: [Function: jumps] }
const cat2 = {
lives: 5,
jumps: () => {
console.log(lives);
}
};
cat2.jumps(); // 2
console.log(cat2); // { lives: 5, jumps: [Function: jumps] }
const cat3 = {
lives: 5,
jumps: () => {
const lives = 3;
console.log(lives);
}
};
cat3.jumps(); // 3
console.log(cat3); // { lives: 5, jumps: [Function: jumps] }
const cat4 = {
lives: 5,
jumps: function () {
console.log(lives);
}
};
cat4.jumps(); // 2
console.log(cat4); // { lives: 5, jumps: [Function: jumps] }
const cat5 = {
lives: 5,
jumps: function () {
var lives = 4;
console.log(lives);
}
};
cat5.jumps(); // 4
console.log(cat5); // { lives: 5, jumps: [Function: jumps] }
const cat6 = {
lives: 5,
jumps: function () {
console.log(this.lives);
}
};
cat6.jumps(); // 5
console.log(cat6); // { lives: 5, jumps: [Function: jumps] }
const cat7 = {
lives: 5,
jumps: function thrownOutOfWindow () {
console.log(this.lives);
}
};
cat7.jumps(); // 5
console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }
}
catCircus();
Questo argomento è fortemente correlato alla bind
funzione integrata e introdotto nelle funzioni freccia ECMAScript 6 . È stato davvero fastidioso, perché per ogni nuovo metodo di "classe" (funzione in realtà) che volevamo usare, dovevamo farlo bind
per avere accesso all'ambito.
JavaScript per impostazione predefinita non imposta il suo ambito di this
funzioni on (non imposta il contesto su this
). Di default devi dire esplicitamente quale contesto vuoi avere.
Le funzioni freccia ottengono automaticamente il cosiddetto ambito lessicale (hanno accesso alla definizione della variabile nel suo blocco contenitore). Quando si usano le funzioni freccia, si lega automaticamente this
al punto in cui la funzione freccia è stata definita in primo luogo e il contesto di queste funzioni freccia è il suo blocco contenitore.
Guarda come funziona in pratica sugli esempi più semplici di seguito.
Prima delle funzioni freccia (nessun ambito lessicale per impostazione predefinita):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const globalScope = programming.getLanguage;
console.log(globalScope()); // Output: undefined
const localScope = programming.getLanguage.bind(programming);
console.log(localScope()); // Output: "JavaScript"
Con le funzioni freccia (ambito lessicale di default):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const arrowFunction = () => {
console.log(programming.getLanguage());
}
arrowFunction(); // Output: "JavaScript"