Jednostavan uvod u leksički opseg u JavaScript-u

Leksički opseg tema je koja plaši mnoge programere. Jedno od najboljih objašnjenja leksičkog opsega može se naći u knjizi Kylea Simpsona You Don't Know JS: Scope and Closures. Međutim, čak i njegovo objašnjenje nedostaje jer ne koristi pravi primjer.

Jedan od najboljih stvarnih primjera kako leksičko opseg funkcionira i zašto je važan, može se naći u poznatom udžbeniku "Struktura i tumačenje računalnih programa" (SICP) Harolda Abelsona i Geralda Jaya Sussmana. Evo poveznice na PDF verziju knjige: SICP.

SICP koristi shemu, dijalekt Lisp, i smatra se jednim od najboljih uvodnih tekstova iz informatike ikad napisanih. U ovom članku želio bih ponovno posjetiti njihov primjer leksičkog opsega pomoću JavaScript-a kao programskog jezika.

Naš primjer

Primjer koji su koristili Abelson i Sussman je izračunavanje kvadratnih korijena Newtonovom metodom. Newtonova metoda djeluje utvrđivanjem uzastopnih aproksimacija za kvadratni korijen broja sve dok aproksimacija ne uđe u granicu tolerancije da bi bila prihvatljiva. Poradimo na primjeru, kao što to rade Abelson i Sussman u SICP-u.

Primjer koji koriste je pronalaženje kvadratnog korijena iz 2. Počinjete pogađanjem kvadratnog korijena iz 2, recimo 1. Poboljšavate ovu pretpostavku dijeljenjem izvornog broja s pretpostavkom, a zatim umjeravanjem tog količnika i trenutne pretpostavke na smislite sljedeću pretpostavku. Zaustavljate se kad dosegnete prihvatljivu razinu aproksimacije. Abelson i Sussman koriste vrijednost 0,001. Evo prolaska kroz prvih nekoliko koraka u izračunu:

Square root to find: 2First guess: 1Quotient: 2 / 1 = 2Average: (2+1) / 2 = 1.5Next guess: 1.5Quotient: 1.5 / 2 = 1.3333Average: (1.3333 + 1.5) / 2 = 1.4167Next guess: 1.4167Quotient: 1.4167 / 2 = 1.4118Average: (1.4167 + 1.4118) / 2 = 1.4142

I tako sve dok nagađanje ne dođe unutar naše aproksimacijske granice, koja za ovaj algoritam iznosi 0,001.

JavaScript funkcija za Newtonovu metodu

Nakon ove demonstracije metode autori u shemi opisuju opći postupak za rješavanje ovog problema. Umjesto da vam pokažem kod sheme, ispisat ću ga u JavaScript:

function sqrt_iter(guess, x) { if (isGoodEnough(guess, x)) { return guess; } else { return sqrt_iter(improve(guess, x), x); }}

Dalje, moramo razraditi nekoliko drugih funkcija, uključujući isGoodEnough () i poboljšati (), zajedno s nekim drugim pomoćnim funkcijama. Krenut ćemo s poboljšanjem (). Evo definicije:

function improve(guess, x) { return average(guess, (x / guess));}

Ova funkcija koristi pomoćni prosjek funkcije (). Evo te definicije:

function average(x, y) { return (x+y) / 2;}

Sada smo spremni definirati funkciju isGoodEnough (). Ova funkcija služi za određivanje kada je naša pretpostavka dovoljno blizu naše aproksimacijske tolerancije (0,001). Evo definicije isGoodEnough ():

function isGoodEnough(guess, x) { return (Math.abs(square(guess) - x)) < 0.001;}

Ova funkcija koristi kvadratnu () funkciju, koju je lako definirati:

function square(x) { return x * x;}

Sada nam treba samo funkcija za pokretanje stvari:

function sqrt(x) { return sqrt_iter(1.0, x);}

Ova funkcija koristi 1.0 kao početnu pretpostavku, što je obično sasvim u redu.

Sada smo spremni testirati svoje funkcije kako bismo provjerili rade li. Učitavamo ih u JS ljusku, a zatim izračunavamo nekoliko kvadratnih korijena:

> .load sqrt_iter.js> sqrt(3)1.7321428571428572> sqrt(9)3.00009155413138> sqrt(94 + 87)13.453624188555612> sqrt(144)12.000000012408687

Čini se da funkcije dobro funkcioniraju. Međutim, ovdje se krije bolja ideja. Sve su ove funkcije napisane neovisno, iako im je namijenjeno da rade zajedno. Vjerojatno nećemo koristiti funkciju isGoodEnough () s bilo kojim drugim skupom funkcija ili samostalno. Također, korisniku je važna samo funkcija sqrt (), jer je ona pozvana da pronađe kvadratni korijen.

Opseg blokova skriva pomoćne funkcije

Rješenje je ovdje koristiti opseg blokova za definiranje svih potrebnih pomoćnih funkcija unutar bloka sqrt () funkcije. Uklonit ćemo kvadrat () i prosjek () iz definicije, jer bi te funkcije mogle biti korisne u drugim definicijama funkcija i nisu toliko ograničene za upotrebu u algoritmu koji implementira Newtonovu metodu. Evo definicije sqrt () funkcije sada s ostalim pomoćnim funkcijama definiranim u opsegu sqrt ():

function sqrt(x) { function improve(guess, x) { return average(guess, (x / guess)); } function isGoodEnough(guess, x) { return (Math.abs(square(guess) - x)) > 0.001; } function sqrt_iter(guess, x) { if (isGoodEnough(guess, x)) { return guess; } else { return sqrt_iter(improve(guess, x), x); } } return sqrt_iter(1.0, x);}

Sada možemo učitati ovaj program u našu ljusku i izračunati neke kvadratne korijene:

> .load sqrt_iter.js> sqrt(9)3.00009155413138> sqrt(2)1.4142156862745097> sqrt(3.14159)1.772581833543688> sqrt(144)12.000000012408687

Primijetite da ne možete pozvati nijednu pomoćnu funkciju izvan funkcije sqrt ():

> sqrt(9)3.00009155413138> sqrt(2)1.4142156862745097> improve(1,2)ReferenceError: improve is not defined> isGoodEnough(1.414, 2)ReferenceError: isGoodEnough is not defined

Budući da su definicije ovih funkcija (poboljšati () i isGoodEnough ()) premještene unutar opsega sqrt (), ne može im se pristupiti na višoj razini. Naravno, možete premjestiti bilo koju definiciju pomoćne funkcije izvan sqrt () funkcije kako biste im pristupili globalno, kao što smo to učinili sa average () i square ().

Uvelike smo poboljšali našu implementaciju Newtonove metode, ali još uvijek možemo učiniti još jednu stvar kako bismo poboljšali našu funkciju sqrt () tako što ćemo je još više pojednostaviti iskorištavanjem leksičkog opsega.

Poboljšanje jasnoće leksičkim opsegom

Koncept leksičkog opsega je da kada je varijabla vezana uz okruženje, drugi postupci (funkcije) koji su definirani u tom okruženju imaju pristup vrijednosti te varijable. To znači da je u sqrt () funkciji parametar x vezan za tu funkciju, što znači da bilo koja druga funkcija definirana unutar tijela sqrt () može pristupiti x.

Znajući to, možemo još više pojednostaviti definiciju sqrt () uklanjanjem svih referenci na x u definicijama funkcija jer je x sada besplatna varijabla i svima njima je dostupan. Evo naše nove definicije sqrt ():

function sqrt(x) { function isGoodEnough(guess) { return (Math.abs(square(guess) - x)) > 0.001; } function improve(guess) { return average(guess, (x / guess)); } function sqrt_iter(guess) { if (isGoodEnough(guess)) { return guess; } else { return sqrt_iter(improve(guess)); } } return sqrt_iter(1.0);}

Jedine reference na parametar x nalaze se u proračunima gdje je potrebna vrijednost x. Učitajmo ovu novu definiciju u ljusku i testirajmo je:

> .load sqrt_iter.js> sqrt(9)3.00009155413138> sqrt(2)1.4142156862745097> sqrt(123+37)12.649110680047308> sqrt(144)12.000000012408687

Lexical scoping and block structure are important features of JavaScript and allow us to construct programs that are easier to understand and manage. This is especially important when we begin to construct larger, more complex programs.