Ricerca…
Osservazioni
Scope è il contesto in cui le variabili vivono e possono essere accessibili da un altro codice nello stesso ambito. Poiché JavaScript può essere utilizzato in gran parte come linguaggio di programmazione funzionale, conoscere l'ambito di variabili e funzioni è importante in quanto aiuta a prevenire bug e comportamenti imprevisti in fase di runtime.
Differenza tra var e let
(Nota: tutti gli esempi che usano let
sono anche validi per const
)
var
è disponibile in tutte le versioni di JavaScript, mentre let
e const
fanno parte di ECMAScript 6 e sono disponibili solo in alcuni browser più recenti .
var
è orientato alla funzione contenitore o allo spazio globale, a seconda di quando è dichiarato:
var x = 4; // global scope
function DoThings() {
var x = 7; // function scope
console.log(x);
}
console.log(x); // >> 4
DoThings(); // >> 7
console.log(x); // >> 4
Ciò significa che "fugge" if
istruzioni e tutti i costrutti di blocchi simili:
var x = 4;
if (true) {
var x = 7;
}
console.log(x); // >> 7
for (var i = 0; i < 4; i++) {
var j = 10;
}
console.log(i); // >> 4
console.log(j); // >> 10
Per confronto, let
è a blocchi:
let x = 4;
if (true) {
let x = 7;
console.log(x); // >> 7
}
console.log(x); // >> 4
for (let i = 0; i < 4; i++) {
let j = 10;
}
console.log(i); // >> "ReferenceError: i is not defined"
console.log(j); // >> "ReferenceError: j is not defined"
Nota che i
e j
sono dichiarati solo nel ciclo for
e quindi non sono dichiarati al di fuori di esso.
Ci sono molte altre differenze cruciali:
Dichiarazione globale delle variabili
Nell'ambito superiore (al di fuori di qualsiasi funzione e blocco), le dichiarazioni var
inseriscono un elemento nell'oggetto globale. let
no:
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
Re-Dichiarazione
Dichiarare una variabile due volte usando var
non produce un errore (anche se è equivalente a dichiararlo una volta):
var x = 4;
var x = 7;
Con let
, questo produce un errore:
let x = 4;
let x = 7;
TypeError: l'identificatore
x
è già stato dichiarato
Lo stesso vale quando y
è dichiarato con var
:
var y = 4;
let y = 7;
TypeError: l'identificatore
y
è già stato dichiarato
Tuttavia, le variabili dichiarate con let possono essere riutilizzate (non dichiarate nuovamente) in un blocco nidificato
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
All'interno del blocco l'esterno i
si può accedere, ma se il blocco all'interno ha un let
dichiarazione per i
, l'esterno i
non è possibile accedere e getterò un ReferenceError
se utilizzato prima della seconda è dichiarato.
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError: i non è definito
sollevamento
Le variabili dichiarate sia con var
con let
vengono issate . La differenza è che una variabile dichiarata con var
può essere referenziata prima del proprio assegnamento, dal momento che viene automaticamente assegnata (con un valore undefined
), ma let
può, in particolare richiede che la variabile venga dichiarata prima di essere invocata:
console.log(x); // >> undefined
console.log(y); // >> "ReferenceError: `y` is not defined"
//OR >> "ReferenceError: can't access lexical declaration `y` before initialization"
var x = 4;
let y = 7;
L'area tra l'inizio di un blocco e una dichiarazione let
o const
è nota come zona morta temporanea e qualsiasi riferimento alla variabile in quest'area causerà un'eccezione ReferenceError
. Ciò accade anche se la variabile viene assegnata prima di essere dichiarata :
y=7; // >> "ReferenceError: `y` is not defined"
let y;
In modalità non rigida, assegnando un valore a una variabile senza alcuna dichiarazione, dichiara automaticamente la variabile nell'ambito globale . In questo caso, invece di y
essere dichiarato automaticamente nel campo di applicazione globale, let
le riserve il nome della variabile ( y
) e non consente alcun accesso o cessione a prima riga in cui si è dichiarata / inizializzato.
chiusure
Quando viene dichiarata una funzione, le variabili nel contesto della sua dichiarazione vengono catturate nel suo ambito. Ad esempio, nel codice seguente, la variabile x
è associata a un valore nello scope esterno, quindi il riferimento a x
viene catturato nel contesto della bar
:
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
Uscita campione:
4
Questo concetto di "cattura" dell'ambito è interessante perché possiamo usare e modificare variabili da un ambito esterno anche dopo l'uscita dall'ambito esterno. Ad esempio, considera quanto segue:
function foo() {
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
return bar;
// x goes out of scope after foo returns
}
var barWithX = foo();
barWithX(); // we can still access x
Uscita campione:
4
Nell'esempio sopra, quando viene chiamato foo
, il suo contesto viene catturato nella bar
funzioni. Quindi, anche dopo il suo ritorno, la bar
può ancora accedere e modificare la variabile x
. La funzione foo
, il cui contesto è catturato in un'altra funzione, si dice che sia una chiusura .
Dati privati
Questo ci permette di fare alcune cose interessanti, come la definizione di variabili "private" che sono visibili solo per una funzione specifica o un insieme di funzioni. Un esempio forzato (ma popolare):
function makeCounter() {
var counter = 0;
return {
value: function () {
return counter;
},
increment: function () {
counter++;
}
};
}
var a = makeCounter();
var b = makeCounter();
a.increment();
console.log(a.value());
console.log(b.value());
Uscita di esempio:
1 0
Quando viene chiamato makeCounter()
, viene salvata un'istantanea del contesto di tale funzione. Tutto il codice all'interno di makeCounter()
utilizzerà makeCounter()
nella loro esecuzione. Due chiamate di makeCounter()
creeranno quindi due diverse istantanee, con la propria copia del counter
.
Espressioni di funzioni invocate immediatamente (IIFE)
Le chiusure sono anche utilizzate per prevenire l'inquinamento dello spazio dei nomi globale, spesso attraverso l'uso di espressioni di funzione immediatamente invocate.
Le espressioni di funzione invocate immediatamente (o, forse più intuitivamente, le funzioni anonime autoeseguite ) sono essenzialmente chiusure chiamate subito dopo la dichiarazione. L'idea generale con gli IIFE è di invocare l'effetto collaterale della creazione di un contesto separato accessibile solo al codice all'interno dell'IFE.
Supponiamo di voler essere in grado di fare riferimento a jQuery
con $
. Considera il metodo ingenuo, senza usare un IIFE:
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
Nell'esempio seguente, viene utilizzato un IIFE per garantire che $
sia associato a jQuery
solo nel contesto creato dalla chiusura:
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
Vedere la risposta canonica su Stackoverflow per ulteriori informazioni sulle chiusure.
sollevamento
Cosa sta sollevando?
Il sollevamento è un meccanismo che sposta tutte le dichiarazioni di variabili e funzioni nella parte superiore del loro ambito. Tuttavia, le assegnazioni variabili avvengono ancora dove erano originariamente.
Ad esempio, considera il seguente codice:
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
Il codice sopra è lo stesso di:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
Si noti che a causa del sollevamento di quanto sopra undefined
non è uguale al not defined
risultante dall'esecuzione:
console.log(foo); // → foo is not defined
Un principio simile si applica alle funzioni. Quando le funzioni sono assegnate a una variabile (es. Un'espressione di funzione ), la dichiarazione della variabile viene issata mentre l'assegnazione rimane nella stessa posizione. I seguenti due frammenti di codice sono equivalenti.
console.log(foo(2, 3)); // → foo is not a function
var foo = function(a, b) {
return a * b;
}
var foo;
console.log(foo(2, 3)); // → foo is not a function
foo = function(a, b) {
return a * b;
}
Quando si dichiarano le istruzioni di funzione , si verifica uno scenario diverso. A differenza delle istruzioni di funzione, le dichiarazioni di funzione vengono issate nella parte superiore del loro ambito. Considera il seguente codice:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
Il codice precedente è lo stesso del frammento di codice successivo a causa del sollevamento:
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
Ecco alcuni esempi di cosa è e cosa non sta sollevando:
// Valid code:
foo();
function foo() {}
// Invalid code:
bar(); // → TypeError: bar is not a function
var bar = function () {};
// Valid code:
foo();
function foo() {
bar();
}
function bar() {}
// Invalid code:
foo();
function foo() {
bar(); // → TypeError: bar is not a function
}
var bar = function () {};
// (E) valid:
function foo() {
bar();
}
var bar = function(){};
foo();
Limitazioni di sollevamento
L'inizializzazione di una variabile non può essere sollevata o in semplici dichiarazioni di non sollevamento di JavaScript non inizializzate.
Ad esempio: i seguenti script daranno risultati diversi.
var x = 2;
var y = 4;
alert(x + y);
Questo ti darà un risultato di 6. Ma questo ...
var x = 2;
alert(x + y);
var y = 4;
Questo ti darà un'uscita di NaN. Poiché inizializziamo il valore di y, il sollevamento JavaScript non sta accadendo, quindi il valore y sarà indefinito. Il JavaScript considererà che y non è stato ancora dichiarato.
Quindi il secondo esempio è lo stesso di sotto.
var x = 2;
var y;
alert(x + y);
y = 4;
Questo ti darà un'uscita di NaN.
Uso dei cicli di accesso anziché di var (esempio dei gestori di clic)
Diciamo che abbiamo bisogno di aggiungere un pulsante per ogni pezzo di array loadedData
(ad esempio, ogni pulsante dovrebbe essere un cursore che mostra i dati, per semplicità, ci limiteremo ad avvisare un messaggio). Si può provare qualcosa di simile a questo:
for(var i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Ma invece di avvisare, ogni pulsante causerà il
TypeError: loadedData [i] non è definito
errore. Questo perché l'ambito di i
è l'ambito globale (o un ambito di funzione) e dopo il ciclo, i == 3
. Ciò di cui abbiamo bisogno non è "ricordare lo stato di i
". Questo può essere fatto usando let
:
for(let i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
Un esempio di dati loadedData
da testare con questo codice:
var loadedData = [
{ label:"apple", content:"green and round" },
{ label:"blackberry", content:"small black or blue" },
{ label:"pineapple", content:"weird stuff.. difficult to explain the shape" }
];
Un violino per illustrare questo
Invocazione del metodo
Invocando una funzione come metodo di un oggetto, il valore di this
sarà quell'oggetto.
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
Ora possiamo invocare la stampa come metodo di obj. this
sarà obj
obj.print();
Questo registrerà quindi:
foo
Invocazione anonima
Invocando una funzione come funzione anonima, this
sarà l'oggetto globale ( self
nel browser).
function func() {
return this;
}
func() === window; // true
Nella modalità rigorosa di ECMAScript 5 , this
non sarà undefined
se la funzione è invocata in modo anonimo.
(function () {
"use strict";
func();
}())
Questo uscirà
undefined
Invocazione costruttore
Quando una funzione viene invocata come costruttore con la new
parola chiave, this
prende il valore dell'oggetto che viene costruito
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
Questo registrerà
{nome: "Foo"}
Invocazione di funzione di freccia
Quando si utilizzano funzioni freccia this
prende il valore dal contesto di esecuzione racchiude è this
(cioè, this
funzioni freccia ha ambito lessicale piuttosto che il solito scope dinamico). Nel codice globale (codice che non appartiene a nessuna funzione) sarebbe l'oggetto globale. E continua così, anche se invochi la funzione dichiarata con la notazione a freccia da uno qualsiasi degli altri metodi qui descritti.
var globalThis = this; //"window" in a browser, or "global" in Node.js
var foo = (() => this);
console.log(foo() === globalThis); //true
var obj = { name: "Foo" };
console.log(foo.call(obj) === globalThis); //true
Guarda come this
eredita il contesto piuttosto che fare riferimento all'oggetto su cui è stato chiamato il metodo.
var globalThis = this;
var obj = {
withoutArrow: function() {
return this;
},
withArrow: () => this
};
console.log(obj.withoutArrow() === obj); //true
console.log(obj.withArrow() === globalThis); //true
var fn = obj.withoutArrow; //no longer calling withoutArrow as a method
var fn2 = obj.withArrow;
console.log(fn() === globalThis); //true
console.log(fn2() === globalThis); //true
Applica e chiama sintassi e invocazione.
I metodi apply
e call
in ogni funzione consentono di fornire un valore personalizzato per this
.
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
Si potrebbe notare che la sintassi per entrambe le invocazioni utilizzate sopra sono le stesse. cioè la firma sembra simile.
Ma c'è una piccola differenza nel loro utilizzo, dal momento che abbiamo a che fare con le funzioni e cambiando i loro ambiti, abbiamo ancora bisogno di mantenere gli argomenti originali passati alla funzione. Sia apply
che call
supportano gli argomenti alla funzione target come segue:
function speak() {
var sentences = Array.prototype.slice.call(arguments);
console.log(this.name+": "+sentences);
}
var person = { name: "Sunny" };
speak.apply(person, ["I", "Code", "Startups"]); // >> "Sunny: I Code Startups"
speak.call(person, "I", "<3", "Javascript"); // >> "Sunny: I <3 Javascript"
Si noti che apply
consente di passare una Array
o l'oggetto arguments
(array-like) come l'elenco degli argomenti, mentre la call
bisogno di passare ogni argomento separatamente.
Questi due metodi ti danno la libertà di avere la fantasia che desideri, come implementare una versione povera del bind
nativo di ECMAScript per creare una funzione che sarà sempre chiamata come metodo di un oggetto da una funzione originale.
function bind (func, obj) {
return function () {
return func.apply(obj, Array.prototype.slice.call(arguments, 1));
}
}
var obj = { name: "Foo" };
function print() {
console.log(this.name);
}
printObj = bind(print, obj);
printObj();
Questo registrerà
"Pippo"
La funzione bind
ha molte cose da fare
-
obj
sarà usato come valore dithis
- inoltrare gli argomenti alla funzione
- e quindi restituire il valore
Invocazione rilegata
Il metodo di bind
di ogni funzione ti consente di creare una nuova versione di quella funzione con il contesto strettamente legato a un oggetto specifico. È particolarmente utile forzare una funzione per essere chiamata come metodo di un oggetto.
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
Questo registrerà:
bar