2012-04-08 18 views
25

JavaScript posiada zakresy leksykalne, co oznacza, że ​​zmienne nielokalne dostępne z poziomu funkcji są rozwiązywane na zmienne obecne w zasięgu rodziców tej funkcji, gdy została ona zdefiniowana. Jest to przeciwieństwo dynamicznego zakresu, w którym zmienne nielokalne uzyskiwane z funkcji są rozwiązywane na zmienne obecne w zasięgu wywołania tej funkcji, gdy jest wywoływana.Czy możliwe jest osiągnięcie dynamicznego zakresu w JavaScript bez odwoływania się do 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? 

Powyższe wydruków programów 1, a następnie 2 w lexically określania zakresów języka oraz drukuje 3, a następnie 1 w dynamicznego zakresu języka. Ponieważ JavaScript jest zawężona leksykalnie wypisze 1, a następnie 2, jak pokazano poniżej:

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

Chociaż JavaScript nie obsługuje Zakresy dynamiczne możemy wdrożyć go za pomocą eval następująco:

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

Chciałbym wiedzieć, czy istnieje inny możliwy sposób osiągnięcia tego samego rezultatu bez odwoływania się do eval.

Edit: To co staram się realizować bez użycia 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());

wiem, że nie jest to bardzo dobry przykład, ale ogólna idea jest użycie dynamiczne ustalanie zakresu w celu tworzenia zamknięć. Myślę, że ten wzór ma duży potencjał.

+1

Ładnie napisane, interesujące pytanie. Choć prosi o obiektywną odpowiedź, chyba że ktoś pokaże, jak można to zrobić, podejrzewam, że wywoła to wiele dyskusji i subiektywnych odpowiedzi, dlaczego nie można lub nie należy tego robić. Co doprowadziło cię do tego pytania? –

+0

@ChrisWesseling - zaktualizowałem swoje pytanie, aby pokazać, co skłoniło mnie do napisania tego. Powyższy program jest całkowicie poprawny i działa na wszystkich platformach. Wierzę, że ma duży potencjał do tworzenia wzorców klasowych i wielu innych rzeczy. To zależy od ogółu społeczeństwa, aby mądrze go używać. Być może jest to jeden z ważnych powodów używania 'eval'. –

+0

Powodem, dla którego chcę korzystać z dynamicznego określania zakresu, jest możliwość wstrzyknięcia prywatnych zmiennych do zakresu funkcji. Na przykład w powyższym programie mogę również przekazać zmienną zwaną 'uber', która wskazuje na rodzica danej klasy. Ta zmienna powinna być dostępna dla zakresu klasy, ale nie powinna być publicznie dostępna. Dlatego nie mogę po prostu ustawić go na instancji klasy i nazwać go dniem. W ten sposób obejść rondo. –

Odpowiedz

10

Wyszukiwanie atrybutów przechodzi przez łańcuch prototypów, który pasuje dość dobrze do zakresów dynamicznych. Po prostu przekazuj własne środowisko zmiennych o dynamicznym zakresie, aby używać ich zamiast używać leksykalnych zakresów 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

Twój kod jest bardzo mylący. Umysł dostarczając znaczących komentarzy, aby wyjaśnić, co robisz? –

+0

@AititMShah Komentowane. I przeczytaj [Wprowadzenie do JavaScript zorientowanego na obiekt] (https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript). – ephemient

+0

W porządku W końcu zrozumiałem twój kod. Używasz konstruktora o nazwie 'dyn' do symulacji zasięgu globalnego.Każda funkcja ma formalny parametr o nazwie 'env', który jest równoważny z obiektem aktywacji tej funkcji. Ten obiekt 'env' jest dostarczany przez wywołującego. Dla funkcji 'f' dostarczamy globalną instancję' dyn'. Dla 'g' dostarczamy egzemplarz" copy-on-write "globalnego' dyn' pchającego pierwszy na łańcuch prototypów. Jest to intuicyjna odpowiedź, a wyszukiwanie zakresu przypomina przebieg łańcucha prototypów. Plus jeden do znalezienia alternatywy zamiast uciekania się do używania 'eval'. =) –

2

Nie sądzę.

Tak nie działa język. Aby odwoływać się do tych informacji o stanie, musisz użyć czegoś innego niż zmienne. Uważam, że najbardziej "naturalnym" sposobem jest używanie właściwości this.

0

Można symulować dynamiczne określanie zakresu przy użyciu zmiennych globalnych, jeśli masz sposób na wykonanie cukru syntaktycznego (np. Makra z gensyms) i jeśli odświeżysz ochronę.

Makro może zostać ponownie powiązane z dynamiczną zmienną, zapisując jej wartość w ukrytym leksykalnym, a następnie przypisując nową wartość. Kod zapobiegający rozwijaniu zapewnia, że ​​niezależnie od tego, jak ten blok zostanie zakończony, pierwotna wartość globalna zostanie przywrócona.

Lisp pseudokod:

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

Oczywiście, to nie jest thread-safe sposób robi dynamiczny zakres, ale jeśli nie robią gwintowania, to zrobi! Chcemy go ukryć za makro takich jak:

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

Więc jeśli masz odpoczynek zabezpieczyć w JavaScript, a makro preprocesora wygenerować trochę cukru składniowej (więc nie jesteś otwarty ręcznie kodowania wszystkie swoje oszczędza i odpoczynek -bezpieczne przywracanie) może być wykonalne.

+0

Czy mógłbyś zadbać o rozwinięcie w prostszy sposób? Może chcesz połączyć mnie z zasobem online? –

+0

Zaktualizowałem komentarz za pomocą ilustracji kodu za pomocą Lisp; mam nadzieję, że pomaga. – Kaz

+0

Chyba lepiej zacznę uczyć się teraz z Lispa. Dzięki za pomoc. Doceniony. =) –

2

W twoim przypadku, zamiast próbować wykorzystać Zakresy dynamiczne ustawić konstruktora, co jeśli użyto zwrotu wartość?

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

Mogłem to zrobić, ale jak wyjaśniłem w moim ostatnim komentarzu do mojego pytania - użycie dynamicznego zakresu pozwala mi wstrzyknąć tyle zmiennych, ile chcę, do zakresu funkcji. Możesz zwrócić tylko jedną wartość. –

0

Wiem, że to nie odpowiada dokładnie na pytanie, ale jest za dużo kodu, aby umieścić go w komentarzu.

Jako alternatywne podejście, możesz zajrzeć do funkcji ExtJS extend. Jak to działa:

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

o właściwościach publicznych zamiast zmiennych prywatnych:

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

Aby dodać notatkę na ten temat:

W JavaScript w dowolnym momencie skorzystać z:

  • deklaracja funkcji lub wyrażenie definicji funkcji, a następnie zmienne lokalne wi Mam Określanie leksykalne.

  • Funkcja konstruktor następnie zmienne lokalne będą odnosić się do zakresu globalnego (code top-level)

  • this to jedyny wbudowany w obiekt w JavaScript, który ma dynamiczną scopingu i ustawiany jest za pomocą kontekst wykonywania (lub wywoływania).

więc odpowiedzieć na pytanie, w JS this już funkcję dynamicznego zakresu języka i nawet nie trzeba naśladować innego.

+2

Jestem wdzięczny, że znam "to". Dziękuję Ci. –

2

Dlaczego nikt nie powiedział this?

Możesz przekazywać zmienne z zakresu wywołującego do wywoływanej funkcji, wiążąc ją z kontekstem.

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); 

Być może warto wspomnieć, że w trybie ścisłym, wszystkie funkcje nie mają „środowisko” wysłał do nich domyślnie (this jest null). W trybie nie ścisłym obiekt globalny jest domyślną wartością dla wywoływanej funkcji.

+0

Ludzie już powiedzieli "to": [Arman] (http://stackoverflow.com/a/16486081/783743), [Thilo] (http://stackoverflow.com/a/10060876/783743). Jeśli się nad tym zastanowisz, "to" jest tylko kolejnym argumentem. Tak naprawdę nie potrzebujesz tego "tego". Po prostu użyj dodatkowego parametru. Właśnie to opisuje [ephemient] (http://stackoverflow.com/a/10061044/783743) w swojej odpowiedzi i to jest powód, dla którego ją zaakceptowałem. Twoja odpowiedź naprawdę nie dodaje żadnej wartości. –

+0

Chyba można to zobaczyć w ten sposób. – user68880

Powiązane problemy