Co to jest zakres leksykalny?


682

Co to jest krótkie wprowadzenie do zakresu leksykalnego?


89
W podcastie 58, Joel zachęca do takich pytań, ponieważ chce, aby SO stało się miejscem, w którym można znaleźć odpowiedzi, nawet jeśli udzielono na nie odpowiedzi w innych miejscach. To ważne pytanie, choć można by powiedzieć, że jest trochę bardziej grzeczne.
Ralph M. Rickenbach

5
@rahul Rozumiem, że to stare pytanie. Ale jestem pewien, że nawet w 2009 r. SO spodziewali się, że pytający włożą jakiś podstawowy wysiłek w jego rozwiązanie. W obecnej formie nie wykazuje żadnego wysiłku. Być może dlatego właśnie zostało to ocenione przez wielu?
PP

13
Możliwe, że pytający nie posługuje się (lub nie) biegle językiem angielskim podczas pisania tego pytania
Martin

27
Pytanie jest grzeczne, po prostu mówi, co chce. Masz swobodę odpowiedzi. Nie ma tu potrzeby nadmiernej polityki.
Markus Siebeneicher

25
Myślę, że takie pytania są świetne, ponieważ buduje zawartość dla SO. IMO, kogo to obchodzi, jeśli pytanie nie ma wysiłku ... odpowiedzi będą miały świetną treść i to jest ważne na tym forum.
Jwan622,

Odpowiedzi:


686

Rozumiem je poprzez przykłady. :)

Po pierwsze, zakres leksykalny (zwany również zakresem statycznym ), w składni podobnej do C:

void fun()
{
    int x = 5;

    void fun2()
    {
        printf("%d", x);
    }
}

Każdy poziom wewnętrzny może uzyskać dostęp do swoich zewnętrznych poziomów.

Istnieje inny sposób, zwany zakresem dynamicznym, używany przez pierwszą implementację Lisp , ponownie w składni podobnej do C:

void fun()
{
    printf("%d", x);
}

void dummy1()
{
    int x = 5;

    fun();
}

void dummy2()
{
    int x = 10;

    fun();
}

Tu funmożna albo dostęp xw dummy1lub dummy2lub dowolny xw dowolnej funkcji rozmowy funz xzadeklarowanym w nim.

dummy1();

wydrukuje 5,

dummy2();

wydrukuje 10.

Pierwszy nazywa się statyczny, ponieważ można go wywnioskować w czasie kompilacji, a drugi nazywa się dynamiczny, ponieważ zakres zewnętrzny jest dynamiczny i zależy od wywołania łańcucha funkcji.

Uważam, że statyczne ustalanie zakresu jest łatwiejsze dla oka. W końcu większość języków poszła w ten sposób, nawet Lisp (może zrobić oba, prawda?). Dynamiczne określanie zakresu jest jak przekazywanie referencji wszystkich zmiennych do wywoływanej funkcji.

Jako przykład tego, dlaczego kompilator nie może wydedukować zewnętrznego zakresu dynamicznego funkcji, rozważmy nasz ostatni przykład. Jeśli napiszemy coś takiego:

if(/* some condition */)
    dummy1();
else
    dummy2();

Łańcuch połączeń zależy od warunku czasu wykonywania. Jeśli to prawda, łańcuch połączeń wygląda następująco:

dummy1 --> fun()

Jeśli warunek jest fałszywy:

dummy2 --> fun()

Zewnętrzny zakres funw obu przypadkach to osoba dzwoniąca plus osoba dzwoniąca i tak dalej .

Wystarczy wspomnieć, że język C nie pozwala na zagnieżdżanie funkcji ani dynamiczne określanie zakresu.


19
Chciałbym również zwrócić uwagę na bardzo łatwy do zrozumienia samouczek, który właśnie znalazłem. Przykład Araka jest fajny, ale może być zbyt krótki dla kogoś, kto potrzebuje więcej przykładów (w rzeczywistości w porównaniu z innymi językami ...). Spójrz. Ważne jest, aby to zrozumieć , ponieważ to słowo kluczowe pozwoli nam zrozumieć zakres leksykalny. howtonode.org/what-is-this
CppLearner

9
To dobra odpowiedź. Ale pytanie jest oznaczone JavaScript. Dlatego uważam, że nie należy tego oznaczać jako przyjętej odpowiedzi. Zakres leksykalny szczególnie w JS jest inny
Boyang

6
Niezwykle dobra odpowiedź. Dziękuję Ci. @Boyang Nie zgadzam się. Nie jestem koderem Lisp, ale uznałem przykład Lisp za pomocny, ponieważ jest to przykład dynamicznego określania zakresu, którego nie otrzymujesz w JS.
dudewad

4
Początkowo myślałem, że przykład jest prawidłowym kodem C i byłem zdezorientowany, czy w C. było dynamiczne określanie zakresu.
Yangshun Tay

2
To wciąż bardzo pomocna odpowiedź, ale myślę, że @Boyang ma rację. Ta odpowiedź odnosi się do „poziomu”, który jest bardziej zgodny z zakresem bloku, jaki ma C. JavaScript domyślnie nie ma zasięgu na poziomie bloku, więc fortypowym problemem jest pętla. Zakres leksykalny JavaScript jest tylko na poziomie funkcji, chyba że używany jest ES6 letlub const.
icc97

274

Spróbujmy najkrótszej możliwej definicji:

Schemat leksykalny określa, w jaki sposób nazwy zmiennych są rozwiązywane w funkcjach zagnieżdżonych: funkcje wewnętrzne zawierają zakres funkcji nadrzędnych, nawet jeśli funkcja nadrzędna powróciła .

To wszystko!


21
Ostatnia część: „nawet jeśli funkcja rodzica wróciła” nazywa się Zamknięcie.
Juanma Menendez

1
Zrozumiałem zakres leksykalny i zamknięcie w jednym zdaniu. Dzięki!!
Loch

63
var scope = "I am global";
function whatismyscope(){
   var scope = "I am just a local";
   function func() {return scope;}
   return func;
}

whatismyscope()()

Powyższy kod zwróci „Jestem tylko lokalnym”. Nie zwróci „Jestem globalny”. Ponieważ funkcja func () liczy, gdzie została pierwotnie zdefiniowana, co jest objęte zakresem funkcji whatismyscope.

Nie będzie to przeszkadzało, niezależnie od tego, jak się nazywa (zakres globalny / nawet z innej funkcji), dlatego wartość zakresu globalnego, którym jestem globalny, nie zostanie wydrukowana.

Nazywa się to zasięgiem leksykalnym, w którym „ funkcje są wykonywane przy użyciu łańcucha zasięgu, który obowiązywał podczas ich definiowania ” - zgodnie z Przewodnikiem po definicji JavaScript.

Zakres leksykalny jest bardzo potężną koncepcją.

Mam nadzieję że to pomoże..:)


3
to bardzo miłe wytłumaczenie Chcę dodać jeszcze jedną rzecz, jeśli napiszesz funkcję func () {return this.scope;}, to zwróci „Jestem globalny”, po prostu użyj tego słowa kluczowego, a zakres wzrośnie
Rajesh Kumar Bhawsar

41

Określanie zakresu leksykalnego (AKA static) odnosi się do określania zakresu zmiennej wyłącznie na podstawie jej pozycji w tekstowym korpusie kodu. Zmienna zawsze odnosi się do środowiska najwyższego poziomu. Dobrze jest to zrozumieć w odniesieniu do zakresu dynamicznego.


41

Zakres określa obszar, w którym dostępne są funkcje, zmienne i tym podobne. Na przykład dostępność zmiennej jest zdefiniowana w jej kontekście, powiedzmy funkcja, plik lub obiekt, w którym są zdefiniowane. Zwykle nazywamy te zmienne lokalne.

Część leksykalna oznacza, że ​​zakres można odczytać z odczytu kodu źródłowego.

Zakres leksykalny jest również znany jako zakres statyczny.

Zakres dynamiczny definiuje zmienne globalne, które można wywoływać lub do których można się odwoływać z dowolnego miejsca po zdefiniowaniu. Czasami nazywane są zmiennymi globalnymi, nawet jeśli zmienne globalne w większości języków programowania mają zakres leksykalny. Oznacza to, że można odczytać kod, że zmienna jest dostępna w tym kontekście. Być może trzeba zastosować klauzulę use lub include, aby znaleźć instancję lub definicję, ale kod / kompilator wie o zmiennej w tym miejscu.

Natomiast w dynamicznym określaniu zakresu najpierw wyszukujesz w funkcji lokalnej, a następnie w funkcji, która wywołała funkcję lokalną, a następnie w funkcji, która wywołała tę funkcję i tak dalej, w górę stosu wywołań. „Dynamiczny” odnosi się do zmiany, polegającej na tym, że stos wywołań może być różny za każdym razem, gdy wywoływana jest dana funkcja, a zatem funkcja może uderzać w różne zmienne w zależności od tego, skąd została wywołana. (patrz tutaj )

Aby zobaczyć ciekawy przykład zakresu dynamicznego, zobacz tutaj .

Więcej informacji można znaleźć tutaj i tutaj .

Kilka przykładów w Delphi / Object Pascal

Delphi ma zakres leksykalny.

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.

Najbardziej zbliżone do zakresu dynamicznego Delphi to para funkcji RegisterClass () / GetClass (). Aby zobaczyć jego użycie, zobacz tutaj .

Powiedzmy, że czas wywołania RegisterClass ([TmyClass]) w celu zarejestrowania określonej klasy nie może być przewidziany przez odczytanie kodu (jest wywoływany w metodzie kliknięcia przycisku wywoływanej przez użytkownika), kod wywołujący GetClass („TmyClass”) otrzyma wynik czy nie. Wywołanie funkcji RegisterClass () nie musi znajdować się w zakresie leksykalnym jednostki za pomocą GetClass ();

Inną możliwością dla zakresu dynamicznego są metody anonimowe (zamknięcia) w Delphi 2009, ponieważ znają zmienne swojej funkcji wywołującej. Nie podąża stamtąd rekurencyjnie ścieżką wywoływania, a zatem nie jest w pełni dynamiczna.


2
Właściwie prywatny jest dostępny w całej jednostce, w której klasa jest zdefiniowana. Właśnie dlatego w D2006 wprowadzono „Ściśle prywatny”.
Marco van de Voort

2
+1 za zwykły język (w przeciwieństwie do skomplikowanego języka i przykładów bez większego opisu)
Pops

36

Uwielbiam w pełni funkcjonalne, niezależne od języka odpowiedzi od ludzi takich jak @Arak. Ponieważ to pytanie zostało oznaczone JavaScript , chciałbym zagłębić się w notatki bardzo specyficzne dla tego języka.

W JavaScript wybieramy zakres:

  • jak jest (bez regulacji zakresu)
  • leksykalny var _this = this; function callback(){ console.log(_this); }
  • uwiązany callback.bind(this)

Myślę, że warto zauważyć, że JavaScript nie ma tak naprawdę dynamicznego zakresu . .binddostosowuje thissłowo kluczowe i jest blisko, ale technicznie nie jest takie samo.

Oto przykład demonstrujący oba podejścia. Robisz to za każdym razem, gdy podejmujesz decyzję o sposobie określania zakresu wywołań zwrotnych, więc dotyczy to obietnic, procedur obsługi zdarzeń i innych.

Leksykalny

Oto, co możesz Lexical Scopingnazwać zwrotnymi w 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
  }
  //...
};

Uwiązany

Innym sposobem na zasięg jest użycie Function.prototype.bind:

var downloadManager = {
  initialize: function() {
    $('.downloadLink').on('click', function () {
      this.startDownload();
    }.bind(this)); // Create a function object bound to `this`
  }
//...

O ile mi wiadomo, metody te są równoważne behawioralnie.


Korzystanie bindnie wpływa na zakres.
Ben Aston

12

Zakres leksykalny: Zmienne zadeklarowane poza funkcją są zmiennymi globalnymi i są widoczne wszędzie w programie JavaScript. Zmienne zadeklarowane w funkcji mają zasięg funkcji i są widoczne tylko dla kodu pojawiającego się w tej funkcji.


12

IBM definiuje to jako:

Część programu lub jednostki segmentu, w której obowiązuje deklaracja. Identyfikator zadeklarowany w procedurze jest znany w tej procedurze i we wszystkich procedurach zagnieżdżonych. Jeśli procedura zagnieżdżona deklaruje element o tej samej nazwie, element zewnętrzny nie jest dostępny w procedurze zagnieżdżonej.

Przykład 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();

Przykład 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();

8

Zakres leksykalny oznacza, że ​​w zagnieżdżonej grupie funkcji funkcje wewnętrzne mają dostęp do zmiennych i innych zasobów ich zakresu nadrzędnego . Oznacza to, że funkcje potomne są leksykalnie powiązane z kontekstem wykonania ich rodziców. Zakres leksykalny jest czasem określany również jako zakres statyczny .

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';
        }
    }
}

Rzeczą, którą zauważysz w zakresie leksykalnym, jest to, że działa on naprzód, co oznacza, że ​​nazwa jest dostępna w kontekstach wykonawczych jego dzieci. Ale nie działa wstecz na swoich rodziców, co oznacza, że ​​rodzice likesnie mogą uzyskać dostępu do zmiennej .

Mówi nam to również, że zmienne o tej samej nazwie w różnych kontekstach wykonywania zyskują pierwszeństwo od góry do dołu stosu wykonania. Zmienna o nazwie podobnej do innej zmiennej w najbardziej wewnętrznej funkcji (najwyższy kontekst stosu wykonawczego) będzie miała wyższy priorytet.

Pamiętaj, że jest to pobierane stąd .


8

W prostym języku zakres leksykalny jest zmienną zdefiniowaną poza twoim zakresem lub górny zakres jest automatycznie dostępny w twoim zasięgu, co oznacza, że ​​nie musisz go tam przekazywać.

Przykład:

let str="JavaScript";

const myFun = () => {
    console.log(str);
}

myFun();

// Dane wyjściowe: JavaScript


2
Najkrótsza i najlepsza odpowiedź dla mnie z przykładem. Można dodać, że funkcje strzałek ES6 rozwiązują problem bind. Dzięki nim bindnie jest już wymagany. Aby uzyskać więcej informacji o tej zmianie, sprawdź stackoverflow.com/a/34361380/11127383
Daniel Danielecki

4

Brakuje ważnej części rozmowy dotyczącej zakresu leksykalnego i dynamicznego : proste wyjaśnienie okresu istnienia zmiennej o zasięgu lub kiedy można uzyskać do niej dostęp.

Dynamiczne określanie zakresu bardzo luźno odpowiada zakresowi określanemu „globalnie” w sposób, w jaki tradycyjnie o nim myślimy (powodem, dla którego przywołuję porównanie między nimi, jest to, że zostało już wspomniane - i nie podoba mi się szczególnie wyjaśnienie powiązanego artykułu ); prawdopodobnie najlepiej nie porównujemy globalnego i dynamicznego - choć podobno, zgodnie z powiązanym artykułem, „... [to] jest przydatne jako substytut zmiennych o zasięgu globalnym”.

Więc, w prostym języku angielskim, jakie jest ważne rozróżnienie między tymi dwoma mechanizmami określania zakresu?

Zakres leksykalny został bardzo dobrze zdefiniowany we wszystkich powyższych odpowiedziach: zmienne o zasięgu leksykalnym są dostępne - lub dostępne - na poziomie lokalnym funkcji, w której zostały zdefiniowane.

Jednak - ponieważ nie jest to cel PO - dynamiczne ustalanie zakresu nie spotkało się z dużym zainteresowaniem, a uwaga, którą otrzymała oznacza, że ​​prawdopodobnie potrzebuje trochę więcej (nie jest to krytyka innych odpowiedzi, ale raczej „och, ta odpowiedź sprawiła, że ​​żałujemy, że nie było trochę więcej ”). Oto trochę więcej:

Dynamiczne określanie zakresu oznacza, że ​​zmienna jest dostępna dla większego programu przez cały czas trwania wywołania funkcji - lub podczas wykonywania funkcji. Naprawdę Wikipedia naprawdę dobrze sobie radzi z wyjaśnieniem różnicy między nimi. Aby go nie zaciemniać, oto tekst opisujący dynamiczne określanie zakresu:

... [I] W zakresie dynamicznym (lub zakresie dynamicznym), jeśli zakres nazwy zmiennej jest pewną funkcją, wówczas jej zasięg jest okresem czasu, w którym funkcja się wykonuje: podczas działania funkcji nazwa zmiennej istnieje i jest powiązany ze swoją zmienną, ale po powrocie funkcji nazwa zmiennej nie istnieje.


3

Zasięg leksykalny oznacza, że ​​funkcja wyszukuje zmienne w kontekście, w którym została zdefiniowana, a nie w zasięgu wokół niej.

Zobacz, jak działa zakres leksykalny w Lisp, jeśli chcesz uzyskać więcej szczegółów. Wybrana odpowiedź Kyle'a Cronina w zmiennych dynamicznych i leksykalnych w Common Lisp jest o wiele jaśniejsza niż odpowiedzi tutaj.

Przypadkowo dowiedziałem się o tym tylko w klasie Lisp, a zdarza się to również w JavaScript.

Uruchomiłem ten kod w konsoli 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)
})();                     //)

Wynik:

5
10
5

3

Zakres leksykalny w JavaScript oznacza, że ​​zmienna zdefiniowana poza funkcją może być dostępna wewnątrz innej funkcji zdefiniowanej po deklaracji zmiennej. Ale przeciwieństwo nie jest prawdą; zmienne zdefiniowane w funkcji nie będą dostępne poza tą funkcją.

Ta koncepcja jest szeroko stosowana w zamknięciach w JavaScript.

Powiedzmy, że mamy poniższy kod.

var x = 2;
var add = function() {
    var y = 1;
    return x + y;
};

Teraz, kiedy wywołasz add () -> to wydrukuje 3.

Tak więc funkcja add () uzyskuje dostęp do zmiennej globalnej, xktóra jest zdefiniowana przed dodaniem funkcji metody. Jest to wywoływane ze względu na zakres leksykalny w JavaScript.


Weź pod uwagę, że fragment kodu dotyczy języka o dynamicznym zakresie. Jeśli add()funkcja zostanie wywołana bezpośrednio po danym fragmencie kodu, wypisze również 3. Zasięg leksykalny nie oznacza po prostu, że funkcja może uzyskać dostęp do zmiennych globalnych poza kontekstem lokalnym. Tak więc przykładowy kod naprawdę nie pomaga pokazać, co oznacza zakres leksykalny. Pokazanie leksykalnego zakresu w kodzie naprawdę wymaga licznika lub przynajmniej wyjaśnienia innych możliwych interpretacji kodu.
C Perkins,

2

Zakres leksykalny odnosi się do leksykonu identyfikatorów (np. Zmiennych, funkcji itp.) Widocznych z bieżącej pozycji na stosie wykonania.

- global execution context
    - foo
    - bar
    - function1 execution context
        - foo2
        - bar2
        - function2 execution context
            - foo3
            - bar3

fooi barzawsze znajdują się w leksykonie dostępnych identyfikatorów, ponieważ są globalne.

Kiedy function1jest wykonywana, że ma dostęp do leksykonu foo2, bar2, foo, i bar.

Kiedy function2jest wykonywana, że ma dostęp do leksykonu foo3, bar3, foo2, bar2, foo, i bar.

Powodem, dla którego funkcje globalne i / lub zewnętrzne nie mają dostępu do identyfikatorów funkcji wewnętrznych, jest to, że wykonanie tej funkcji jeszcze nie nastąpiło i dlatego żaden z jej identyfikatorów nie został przydzielony do pamięci. Co więcej, po uruchomieniu wewnętrznego kontekstu jest on usuwany ze stosu wykonawczego, co oznacza, że ​​wszystkie jego identyfikatory zostały wyrzucone do pamięci i nie są już dostępne.

Wreszcie, dlatego zagnieżdżony kontekst wykonania może ZAWSZE uzyskać dostęp do kontekstu wykonania przodków, a tym samym ma dostęp do większej leksykonu identyfikatorów.

Widzieć:

Specjalne podziękowania dla @ robr3rd za pomoc w uproszczeniu powyższej definicji.


1

Oto inny punkt widzenia na to pytanie, który możemy uzyskać, cofając się o krok i patrząc na rolę określania zakresu w szerszych ramach interpretacji (uruchamianie programu). Innymi słowy, wyobraź sobie, że budujesz interpreter (lub kompilator) dla języka i jesteś odpowiedzialny za obliczenie wyniku, biorąc pod uwagę program i pewne dane wejściowe.

Interpretacja obejmuje śledzenie trzech rzeczy:

  1. Stan - mianowicie zmienne i odnośne lokalizacje pamięci na stercie i stosie.

  2. Operacje w tym stanie - mianowicie każdy wiersz kodu w twoim programie

  3. Środowisko , w którym dana operacja przebiega - mianowicie rzut stanu w operacji.

Tłumacz rozpoczyna się od pierwszego wiersza kodu w programie, oblicza jego środowisko, uruchamia wiersz w tym środowisku i rejestruje jego wpływ na stan programu. Następnie wykonuje kontrolę programu, aby wykonać następny wiersz kodu, i powtarza proces aż do zakończenia programu.

Sposób obliczania środowiska dla dowolnej operacji odbywa się za pomocą formalnego zestawu reguł określonych przez język programowania. Termin „wiązanie” jest często używany do opisania odwzorowania ogólnego stanu programu na wartość w środowisku. Zauważ, że przez „stan ogólny” nie rozumiemy stanu globalnego, ale sumę wszystkich możliwych do osiągnięcia definicji w dowolnym momencie wykonania).

Jest to struktura, w której zdefiniowano problem określania zakresu. Teraz do następnej części naszych opcji.

  • Jako implementator interpretera możesz uprościć swoje zadanie, zbliżając środowisko do stanu programu. W związku z tym środowisko linii kodu byłoby po prostu zdefiniowane przez środowisko poprzedniej linii kodu z zastosowanymi efektami tej operacji, niezależnie od tego, czy poprzednia linia była przypisaniem, wywołaniem funkcji, zwrotem z funkcji, lub struktura kontrolna, taka jak pętla while.

Jest to sedno dynamicznego określania zakresu , w którym środowisko, w którym działa dowolny kod, jest powiązane ze stanem programu określonym przez kontekst jego wykonania.

  • Możesz też pomyśleć o programiście używającym twojego języka i uprościć jego zadanie śledzenia wartości, które może przyjąć zmienna. Istnieje zbyt wiele ścieżek i zbyt duża złożoność w uzasadnieniu wyniku całej egzekucji z przeszłości. Schemat leksykalny pomaga to zrobić, ograniczając bieżące środowisko do części stanu zdefiniowanej w bieżącym bloku, funkcji lub innej jednostce zakresu i jego obiekcie nadrzędnym (tj. Bloku otaczającym bieżący zegar lub funkcji, która wywołała bieżącą funkcję).

Innymi słowy, w zakresie leksykalnym środowisko, które widzi każdy kod, jest powiązane ze stanem powiązanym z zakresem zdefiniowanym jawnie w języku, takim jak blok lub funkcja.


0

Starożytne pytanie, ale oto moje zdanie.

Zakres leksykalny (statyczny) odnosi się do zakresu zmiennej w kodzie źródłowym .

W języku takim jak JavaScript, w którym funkcje mogą być przekazywane i dołączane oraz ponownie dołączane do różnych obiektów, możesz mieć taki zakres, który zależy od tego, kto wywołuje funkcję w danym momencie, ale tak nie jest. Zmiana zakresu w ten sposób byłaby zakresem dynamicznym, a JavaScript tego nie robi, chyba że z thisodniesieniem do obiektu.

Aby zilustrować tę kwestię:

var a='apple';

function doit() {
    var a='aardvark';
    return function() {
        alert(a);
    }
}

var test=doit();
test();

W tym przykładzie zmienna ajest zdefiniowana globalnie, ale jest zaciemniona w doit()funkcji. Ta funkcja zwraca inną funkcję, która, jak widać, opiera się na azmiennej poza własnym zasięgiem.

Jeśli to uruchomisz, przekonasz się, że użyta wartość aardvarknie jest tym , appleco, choć jest objęte zakresem test()funkcji, nie znajduje się w zakresie leksykalnym oryginalnej funkcji. Oznacza to, że zastosowany zakres jest zakresem, jaki pojawia się w kodzie źródłowym, a nie zakresem, w którym funkcja jest faktycznie używana.

Ten fakt może mieć irytujące konsekwencje. Na przykład możesz zdecydować, że łatwiej jest zorganizować swoje funkcje osobno, a następnie użyć ich, gdy nadejdzie odpowiedni czas, na przykład w module obsługi zdarzeń:

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>

Ten przykładowy kod wykonuje jedną z nich. Widać, że z powodu zakresu leksykalnego przycisk Aużywa zmiennej wewnętrznej, a przycisk Bnie. Możesz zagnieżdżać funkcje bardziej, niż byś chciał.

Nawiasem mówiąc, w obu przykładach zauważysz również, że wewnętrzne zmienne o leksykalnym zasięgu pozostają, nawet jeśli funkcja zawierająca funkcję przeszła swój bieg. Nazywa się to zamknięciem i odnosi się do dostępu zagnieżdżonej funkcji do zmiennych zewnętrznych, nawet jeśli funkcja zewnętrzna zakończyła się. JavaScript musi być wystarczająco inteligentny, aby określić, czy te zmienne nie są już potrzebne, a jeśli nie, może je wyrzucić.


-1

Zwykle uczę się na przykładach, a oto coś:

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

-1

Temat ten jest ściśle powiązany z wbudowaną bindfunkcją i wprowadzony w ECMAScript 6 Funkcje strzałek . To było naprawdę denerwujące, ponieważ dla każdej nowej metody „klasy” (właściwie funkcji), której chcieliśmy użyć, musieliśmybind , aby mieć dostęp do zakresu.

JavaScript domyślnie nie określa zakres jego thisfunkcji (to nie ustawia kontekst on this). Domyślnie musisz wyraźnie powiedzieć, który kontekst chcesz mieć.

Funkcje strzałek automatycznie otrzymują tak zwany zakres leksykalny (mają dostęp do definicji zmiennej w jej bloku zawierającym). Podczas korzystania z funkcji strzałek automatycznie wiąże thissię z miejscem, w którym funkcja strzałki została zdefiniowana w pierwszej kolejności, a kontekst tej funkcji strzałki jest zawarte w nim blok.

Zobacz, jak to działa w praktyce na najprostszych przykładach poniżej.

Przed funkcjami strzałek (domyślnie brak zakresu leksykalnego):

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"

Z funkcjami strzałek (domyślnie zakres leksykalny):

const programming = {
  language: "JavaScript",
  getLanguage: function() {
    return this.language;
  }
}

const arrowFunction = () => {
    console.log(programming.getLanguage());
}

arrowFunction(); // Output: "JavaScript"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.