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.

inserisci la descrizione dell'immagine qui

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
5

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

6

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

  1. obj sarà usato come valore di this
  2. inoltrare gli argomenti alla funzione
  3. 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



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow