Könnyű bevezető a Lexical Scoping JavaScript-be

A lexikális hatókör sok programozót megijeszt. A lexikális hatókör egyik legjobb magyarázata megtalálható Kyle Simpson You Don't Know JS: Scope and Closures című könyvében. Még a magyarázata is hiányzik, mert nem használ valódi példát.

Az egyik legjobb valóságos példa arra, hogy a lexikális hatókör hogyan működik és miért fontos, megtalálható Harold Abelson és Gerald Jay Sussman híres tankönyvében: „A számítógépes programok felépítése és értelmezése” (SICP). Itt található egy link a könyv PDF változatához: SICP.

A SICP a Scheme-t használja, amely Lisp nyelvjárása, és az egyik legjobb bevezető informatikai szövegnek számít. Ebben a cikkben szeretnék újból megnézni a lexikális hatókör példáját a JavaScript programozási nyelvként történő használatával.

Példánk

Abelson és Sussman példája a négyzetgyök kiszámítása Newton módszerével. Newton módszere úgy működik, hogy meghatározza az egymás utáni közelítéseket egy szám négyzetgyökéhez, amíg a közelítés egy tűréshatáron belül van ahhoz, hogy elfogadható legyen. Vizsgáljuk meg egy példát, ahogy Abelson és Sussman teszik a SICP-ben.

Az általuk használt példa a 2 négyzetgyökének megtalálása. Először kitalálsz egy 2-es négyzetgyököt, mondjuk az 1-et. Ezt a találgatást úgy javítod, hogy elosztod az eredeti számot a találgatással, majd átlagolod ezt a hányadost és az aktuális találgatást. álljon elő a következő találgatással. Akkor áll le, ha elér egy elfogadható közelítési szintet. Abelson és Sussman a 0,001 értéket használja. Itt van a számítás első néhány lépésének áttekintése:

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

És így tovább, amíg a találgatás a közelítési határunkba esik, ami ennél az algoritmusnál 0,001.

JavaScript-függvény Newton-módszerhez

A módszer bemutatása után a szerzők egy általános eljárást írnak le a probléma megoldására a reakcióvázlaton. Ahelyett, hogy megmutatnám a Séma kódot, kiírom JavaScriptben:

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

Ezután meg kell határoznunk számos más funkciót, köztük az isGoodEnough () és fejlesztést (), valamint néhány más segítő funkciót. Kezdjük a javítással (). Itt van a meghatározás:

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

Ez a függvény egy segítőfüggvény átlagát () használja. Itt van ez a meghatározás:

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

Most készen állunk az isGoodEnough () függvény definiálására. Ez a függvény arra szolgál, hogy meghatározzuk, mikor találgatásunk elég közel van a közelítési toleranciánkhoz (0,001). Itt van az isGoodEnough () meghatározása:

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

Ez a függvény négyzet () függvényt használ, amelyet könnyű meghatározni:

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

Most már csak egy funkcióra van szükségünk a dolgok elindításához:

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

Ez a függvény az 1.0-at használja kiinduló tippként, ami általában rendben van.

Most készen állunk a funkcióink tesztelésére, hogy lássuk, működnek-e. Betöltjük őket egy JS héjba, majd kiszámoljuk néhány négyzetgyököt:

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

Úgy tűnik, hogy a funkciók jól működnek. Van azonban itt egy jobb ötlet. Ezek a függvények egymástól függetlenül íródnak, annak ellenére, hogy egymással együtt működnek. Valószínűleg nem fogjuk használni az isGoodEnough () függvényt más funkciókészlettel vagy önmagában. Ezenkívül az egyetlen funkció, amely a felhasználó számára fontos, az sqrt () függvény, mivel ezt hívják meg egy négyzetgyök megtalálására.

A hatókör blokkolása elrejti a Segítő funkciókat

A megoldás itt az, hogy blokkskopolással definiálja az összes szükséges segítő funkciót az sqrt () függvény blokkjában. El fogjuk távolítani a négyzetet () és az átlagot () a definícióból, mivel ezek a függvények hasznosak lehetnek más függvénydefiníciókban, és nem korlátozódnak olyan korlátozásokra, amelyek Newton módszerét megvalósító algoritmusban használhatók. Itt van az sqrt () függvény meghatározása, az sqrt () hatókörében definiált többi segítő funkcióval együtt:

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

Most betölthetjük ezt a programot a shellünkbe, és kiszámíthatunk néhány négyzetgyöket:

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

Figyelje meg, hogy egyik segítő funkciót sem hívhatja meg az sqrt () függvényen kívülről:

> 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

Mivel ezeknek a függvényeknek a definícióit (javít () és isGoodEnough ()) áthelyeztük az sqrt () hatókörébe, magasabb szinten nem érhetők el. Természetesen bármelyik segítőfüggvény-meghatározást áthelyezheti az sqrt () függvényen kívülre, hogy globálisan hozzáférhessen hozzájuk, ahogy mi tettük az átlag () és a négyzet () esetén.

Nagyban javítottuk a Newton-módszer megvalósítását, de még mindig tehetünk még valamit az sqrt () függvény javításáért, még egyszerűbbé téve a lexikális hatókör előnyeit.

Az egyértelműség javítása a lexikális hatókörrel

A lexikális hatókör mögött az áll, hogy amikor egy változó egy környezethez kötődik, akkor az adott környezetben meghatározott más eljárások (függvények) hozzáférhetnek a változó értékéhez. Ez azt jelenti, hogy az sqrt () függvényben az x paraméter ehhez a függvényhez van kötve, vagyis az sqrt () törzsében definiált bármely más függvény hozzáférhet az x-hez.

Ennek ismeretében még jobban leegyszerűsíthetjük az sqrt () definícióját azáltal, hogy a függvénydefiníciókban az összes x-re való hivatkozást eltávolítjuk, mivel az x ma már szabad változó, és mindegyikük számára hozzáférhető. Itt van az sqrt () új definíciója:

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

Az x paraméterre csak azokban a számításokban hivatkozhatunk, ahol x értékére van szükség. Töltsük be ezt az új meghatározást a shellbe, és teszteljük:

> .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.