2012-04-08 18 views
25

JavaScript ha uno scope lessicale che significa che le variabili non locali a cui si accede da una funzione vengono risolte in variabili presenti nello scope dei genitori di quella funzione quando è stata definita. Questo è in contrasto con lo scope dinamico in cui le variabili non locali accessibili all'interno di una funzione vengono risolte in variabili presenti nell'ambito di chiamata di quella funzione quando viene chiamato.È possibile raggiungere lo scoping dinamico in JavaScript senza ricorrere a eval?

x=1 
function g() { echo $x ; x=2 ; } 
function f() { local x=3 ; g ; } 
f # does this print 1, or 3? 
echo $x # does this print 1, or 2? 

Le stampe di cui sopra del programma 1 e poi 2 in una lingua con scope lessicale, e la stampa 3 e poi 1 in una lingua ambito in modo dinamico. Dal momento che JavaScript è lessicalmente ambito verrà stampata 1 e poi 2 come illustrato di seguito:

var print = x => console.log(x); 
 

 
var x = 1; 
 

 
function g() { 
 
    print(x); 
 
    x = 2; 
 
} 
 

 
function f() { 
 
    var x = 3; 
 
    g(); 
 
} 
 

 
f();   // prints 1 
 

 
print(x);  // prints 2

Anche se JavaScript non supporta scoping dinamico possiamo attuarlo utilizzando eval come segue:

var print = x => console.log(x); 
 

 
var x = 1; 
 

 
function g() { 
 
    print(x); 
 
    x = 2; 
 
} 
 

 
function f() { 
 
    // create a new local copy of `g` bound to the current scope 
 
    // explicitly assign it to a variable since functions can be unnamed 
 
    // place this code in the beginning of the function - manual hoisting 
 
    var g_ = eval("(" + String(g) + ")"); 
 
    var x = 3; 
 
    g_(); 
 
} 
 

 
f();       // prints 3 
 

 
print(x);     // prints 1

Vorrei sapere se esiste un altro modo possibile per ottenere lo stesso risultato senza ricorrere a eval.

Edit: Questo è quello che sto cercando di attuare senza utilizzare eval:

var print = x => console.log(x); 
 

 
function Class(clazz) { 
 
    return function() { 
 
     var constructor; 
 
     var Constructor = eval("(" + String(clazz) + ")"); 
 
     Constructor.apply(this, arguments); 
 
     constructor.apply(this, arguments); 
 
    }; 
 
} 
 

 
var Rectangle = new Class(function() { 
 
    var width, height; 
 

 
    constructor = function (w, h) { 
 
     width = w; 
 
     height = h; 
 
    }; 
 

 
    this.area = function() { 
 
     return width * height; 
 
    }; 
 
}); 
 

 
var rectangle = new Rectangle(2, 3); 
 
print(rectangle.area());

So che non è un buon esempio, ma l'idea generale è di usare scoping dinamico per creare chiusure. Penso che questo modello abbia un grande potenziale.

+1

Ben scritta, domanda interessante. Sebbene chieda una risposta obiettiva, a meno che qualcuno non mostri come può essere fatto, sospetto che provocherà molto dibattito e risposte soggettive sul perché non può o non dovrebbe essere fatto. Cosa ti ha portato a questa domanda? –

+0

@ChrisWesseling - Ho aggiornato la mia domanda per mostrare cosa mi ha fatto postare. Il programma di cui sopra è perfettamente valido e funziona su tutte le piattaforme. Credo che abbia molte potenzialità per creare modelli di classe e molte altre cose. Spetta al pubblico usarlo saggiamente. Forse uno degli unici validi motivi per usare 'eval'. –

+0

Il motivo per cui voglio utilizzare lo scoping dinamico è che posso iniettare variabili private nell'ambito di una funzione. Per esempio, nel mio programma precedente posso anche passare una variabile chiamata 'uber' che punta al genitore della classe data. Questa variabile dovrebbe essere accessibile allo scope di classe ma non dovrebbe essere accessibile al pubblico. Quindi non posso semplicemente impostarlo sull'istanza della classe e chiamarlo un giorno. Quindi la soluzione alternativa alla rotonda. –

risposta

10

La ricerca degli attributi passa attraverso la catena del prototipo, che corrisponde abbastanza bene agli ambiti dinamici. Basta passare il proprio ambiente di variabili con scope dinamiche da utilizzare invece di utilizzare lo scope lessicale di Javascript.


// Polyfill for older browsers. Newer ones already have Object.create. 
if (!Object.create) { 
    // You don't need to understand this, but 
    Object.create = function(proto) { 
    // this constructor does nothing, 
    function cons() {} 
    // and we assign it a prototype, 
    cons.prototype = proto; 
    // so that the new object has the given proto without any side-effects. 
    return new cons(); 
    }; 
} 

// Define a new class 
function dyn() {} 
// with a method which returns a copy-on-write clone of the object. 
dyn.prototype.cow = function() { 
    // An empty object is created with this object as its prototype. Javascript 
    // will follow the prototype chain to read an attribute, but set new values 
    // on the new object. 
    return Object.create(this); 
} 

// Given an environment, read x then write to it. 
function g(env) { 
    console.log(env.x); 
    env.x = 2; 
} 
// Given an environment, write x then call f with a clone. 
function f(env) { 
    env.x = 3; 
    g(env.cow()); 
} 

// Create a new environment. 
var env = new dyn(); 
// env -> {__proto__: dyn.prototype} 
// Set a value in it. 
env.x = 1; 
// env -> {x: 1} // Still has dyn.prototype, but it's long so I'll leave it out. 

f(env.cow()); 
// f(): 
// env -> {__proto__: {x: 1}} // Called with env = caller's env.cow() 
// > env.x = 3 
// env -> {x: 3, __proto__: {x: 1}} // New value is set in current object 
// g(): 
//  env -> {__proto__: {x: 3, __proto__: {x: 1}}} // caller's env.cow() 
//  env.x -> 3 // attribute lookup follows chain of prototypes 
//  > env.x = 2 
//  env -> {x: 2, __proto__: {x: 3, __proto__: {x: 1}}} 

console.log(env.x); 
// env -> {x: 1} // still unchanged! 
// env.x -> 1 
+0

Il tuo codice è molto confuso. Mente fornisce commenti significativi per spiegare cosa stai facendo? –

+0

@AaditMShah Commentato. Andate a leggere [Introduzione al JavaScript orientato agli oggetti] (https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript). – ephemient

+0

Bene, finalmente ho capito il tuo codice. Stai usando un costruttore chiamato 'dyn' per simulare un ambito globale.Ogni funzione ha un parametro formale chiamato 'env' che è equivalente all'oggetto di attivazione di quella funzione. Questo oggetto 'env' è fornito dal chiamante. Per la funzione 'f' forniamo l'istanza globale di' dyn'. Per 'g' forniamo l'istanza copy-on-write del' dyn' globale che spinge il primo sulla catena del prototipo. È una risposta intuitiva e la ricerca dell'ambito assomiglia a una catena di prototipi. Più uno per trovare un'alternativa invece di ricorrere all'utilizzo di 'eval'. =) –

2

Non credo.

Non è così che funziona la lingua. Devi usare qualcosa di diverso dalle variabili per fare riferimento a queste informazioni di stato. Il modo più "naturale" di utilizzare le proprietà di this, credo.

0

È possibile simulare lo scope dinamico utilizzando le variabili globali, se si dispone di un modo per fare zucchero sintattico (ad esempio macro con gensyms) e se si dispone di protezione contro il caricamento.

La macro può apparire per riassociare la variabile dinamica salvandone il valore in un lessicale nascosto e quindi assegnando un nuovo valore. Il codice di unwind-protect garantisce che, indipendentemente dal modo in cui il blocco termina, verrà ripristinato il valore originale del globale.

pseudocodice Lisp:

(let ((#:hidden-local dynamic-var)) 
    (unwind-protect 
    (progn (setf dynamic-var new-value) 
      body of code ...) 
    (set dynamic-var #:hidden-local))) 

Naturalmente, questo non è un modo thread-safe di fare scope dinamico, ma se non stai facendo threading, lo farà! Vorremmo nasconderlo dietro una macro come:

(dlet ((dynamic-var new-value)) 
    body of code ...) 

Quindi, se avete rilassarsi proteggere in Javascript, e un preprocessore macro per generare po 'di zucchero sintattico (quindi non sei manualmente open-codifica tutti i vostri salva e distendersi -protected ripristini) potrebbe essere fattibile.

+0

Ti piacerebbe elaborare in termini più semplici? Forse collegarmi a una risorsa online? –

+0

Ho aggiornato il commento con un'illustrazione di codice usando Lisp; Spero possa aiutare. – Kaz

+0

Credo che sia meglio iniziare a imparare Lisp ora. Grazie per l'aiuto però. Apprezzato. =) –

2

Nel tuo caso, invece di cercare di utilizzare scoping dinamico per impostare il costruttore, che cosa se è stato utilizzato il ritorno valore?

function Class(clazz) { 
    return function() { 
     clazz.apply(this, arguments).apply(this, arguments); 
    }; 
} 

var Rectangle = new Class(function() { 
    var width, height; 

    this.area = function() { 
     return width * height; 
    }; 

    // Constructor 
    return function (w, h) { 
     width = w; 
     height = h; 
    }; 
}); 

var rectangle = new Rectangle(2, 3); 
console.log(rectangle.area()); 
+0

Potrei farlo ma, come ho spiegato nel mio ultimo commento alla mia domanda, l'utilizzo dell'ambito dinamico mi consente di iniettare tutte le variabili che voglio nell'ambito di una funzione. Puoi solo restituire un valore. –

0

So che questo non risponde esattamente alla domanda, ma è troppo codice per inserire un commento.

Come approccio alternativo, è possibile esaminare la funzione extend di ExtJS. Ecco come funziona:

var Rectangle = Ext.extend(Object, { 
    constructor: function (w, h) { 
     var width = w, height = h; 
     this.area = function() { 
      return width * height; 
     }; 
    } 
}); 

Con proprietà pubbliche, invece di variabili private:

var Rectangle = Ext.extend(Object, { 
    width: 0, 
    height: 0, 

    constructor: function (w, h) { 
     this.width = w; 
     this.height = h; 
    }, 

    area: function() { 
     return this.width * this.height; 
    } 
}); 
12

Per aggiungere una nota su questo argomento:

In JavaScript ogni volta che si fanno uso di:

  • dichiarazione dichiarazione di funzione o espressione di definizione di funzione quindi variabili locali wi ll Scoping Lexico.

  • Funzione costruttore allora variabili locali farà riferimento alla portata globale (codice di primo livello)

  • this è l'unico oggetto incorporato in JavaScript che ha una dinamica scoping e viene impostato attraverso il contesto di esecuzione (o invocazione).

Quindi, per rispondere alla tua domanda, In JS il this è già scope dinamico caratteristica della lingua e ancora non hanno bisogno di emulare un altro.

+2

Sono grato di sapere "questo". Grazie. –

2

Perché nessuno ha detto this?

È possibile passare variabili dal campo di applicazione rimettere in funzione chiamata legandosi un contesto.

function called_function() { 
    console.log(`My env ${this} my args ${arguments}`, this, arguments); 
    console.log(`JS Dynamic ? ${this.jsDynamic}`); 
} 

function calling_function() { 
    const env = Object.create(null); 
    env.jsDynamic = 'really?'; 

    ... 

    // no environment 
    called_function('hey', 50); 

    // passed in environment 
    called_function.bind(env)('hey', 50); 

Forse vale la pena ricordare che in modalità rigorosa, tutte le funzioni non hanno alcun "ambiente" inviato loro per impostazione predefinita (this è nullo). In modalità non rigida, l'oggetto globale è il valore predefinito this per una funzione chiamata.

+0

Le persone hanno già detto "questo": [Arman] (http://stackoverflow.com/a/16486081/783743), [Thilo] (http://stackoverflow.com/a/10060876/783743). Se ci pensi "questo" è solo un altro argomento. Quindi, non hai davvero bisogno di questo. Basta usare un parametro extra. Questo è esattamente ciò che [l'effimero] (http://stackoverflow.com/a/10061044/783743) descrive nella sua risposta e questa è la ragione per cui l'ho accettato. La tua risposta in realtà non aggiunge alcun valore. –

+0

Immagino che tu possa vederlo in questo modo. – user68880