En let introduktion til Lexical Scoping i JavaScript

Lexikal scoping er et emne, der skræmmer mange programmører. En af de bedste forklaringer på leksikalsk scoping findes i Kyle Simpsons bog You Don't Know JS: Scope and Closures. Men selv hans forklaring mangler, fordi han ikke bruger et rigtigt eksempel.

Et af de bedste reelle eksempler på, hvordan leksikalskopering fungerer, og hvorfor det er vigtigt, findes i den berømte lærebog, "The Structure and Interpretation of Computer Programs" (SICP) af Harold Abelson og Gerald Jay Sussman. Her er et link til en PDF-version af bogen: SICP.

SICP bruger Scheme, en dialekt af Lisp, og betragtes som en af ​​de bedste indledende datalogiske tekster, der nogensinde er skrevet. I denne artikel vil jeg gerne se deres eksempel på leksikalsk scoping igen ved hjælp af JavaScript som programmeringssprog.

Vores eksempel

Eksemplet, som Abelson og Sussman anvendte, beregner kvadratrødder ved hjælp af Newtons metode. Newtons metode fungerer ved at bestemme successive tilnærmelser for kvadratroden af ​​et tal, indtil tilnærmelsen kommer inden for en tolerancegrænse for at være acceptabel. Lad os arbejde igennem et eksempel, som Abelson og Sussman gør i SICP.

Eksemplet, de bruger, er at finde kvadratroden på 2. Du starter med at gætte kvadratroden på 2, siger 1. Du forbedrer dette gæt ved at dividere det oprindelige tal med gæt og derefter gennemsnitlig kvotienten og det aktuelle gæt til kom med det næste gæt. Du stopper, når du når et acceptabelt niveau af tilnærmelse. Abelson og Sussman bruger værdien 0,001. Her er en gennemgang af de første par trin i beregningen:

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

Og så videre, indtil gættet kommer inden for vores tilnærmelsesgrænse, som for denne algoritme er 0,001.

En JavaScript-funktion til Newtons metode

Efter denne demonstration af metoden beskriver forfatterne en generel procedure til løsning af dette problem i skema. I stedet for at vise dig ordningskoden, skriver jeg den ud i JavaScript:

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

Dernæst er vi nødt til at uddybe flere andre funktioner, herunder isGoodEnough () og forbedre () sammen med nogle andre hjælperfunktioner. Vi starter med forbedring (). Her er definitionen:

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

Denne funktion bruger et hjælpefunktionsgennemsnit (). Her er den definition:

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

Nu er vi klar til at definere isGoodEnough () -funktionen. Denne funktion tjener til at bestemme, hvornår vores gæt er tæt nok på vores tilnærmelsestolerance (0,001). Her er definitionen af ​​isGoodEnough ():

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

Denne funktion bruger en firkantet () funktion, som er let at definere:

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

Nu er alt, hvad vi har brug for, en funktion til at få tingene i gang:

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

Denne funktion bruger 1.0 som et startgætte, hvilket normalt kun er fint.

Nu er vi klar til at teste vores funktioner for at se, om de fungerer. Vi indlæser dem i en JS-skal og beregner derefter et par kvadratrødder:

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

Funktionerne ser ud til at fungere godt. Der er dog en bedre idé, der lurer her. Disse funktioner er alle skrevet uafhængigt, selvom de er beregnet til at arbejde sammen med hinanden. Vi vil sandsynligvis ikke bruge isGoodEnough () -funktionen med andre funktioner eller alene. Den eneste funktion, der betyder noget for brugeren, er også funktionen sqrt (), da det er den, der bliver kaldt for at finde en kvadratrod.

Bloker scoping skjuler hjælpefunktioner

Løsningen her er at bruge blokscoping til at definere alle de nødvendige hjælperfunktioner inden for blokken i sqrt () -funktionen. Vi vil fjerne kvadrat () og gennemsnit () fra definitionen, da disse funktioner kan være nyttige i andre funktionsdefinitioner og ikke er så begrænsede til brug i en algoritme, der implementerer Newtons metode. Her er definitionen af ​​funktionen sqrt () nu med de andre hjælpefunktioner defineret inden for omfanget af 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);}

Vi kan nu indlæse dette program i vores skal og beregne nogle kvadratrødder:

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

Bemærk, at du ikke kan ringe til nogen af ​​hjælpefunktionerne uden for sqrt () -funktionen:

> 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

Da definitionerne af disse funktioner (forbedre () og isGoodEnough ()) er blevet flyttet inden for omfanget af sqrt (), kan de ikke tilgås på et højere niveau. Naturligvis kan du flytte en hvilken som helst af hjælpefunktionsdefinitionerne uden for sqrt () -funktionen for at få adgang til dem globalt, som vi gjorde med gennemsnit () og firkant ().

Vi har forbedret vores implementering af Newtons metode kraftigt, men der er stadig en ting mere, vi kan gøre for at forbedre vores sqrt () -funktion ved at forenkle den endnu mere ved at udnytte det leksikale omfang.

Forbedring af klarhed med Lexical Scope

Konceptet bag leksikalt omfang er, at når en variabel er bundet til et miljø, har andre procedurer (funktioner), der er defineret i dette miljø, adgang til variabelens værdi. Dette betyder, at parameteren x er bundet til denne funktion i funktionen sqrt (), hvilket betyder, at enhver anden funktion, der er defineret i kroppen af ​​sqrt (), kan få adgang til x.

Når vi ved dette, kan vi forenkle definitionen af ​​sqrt () endnu mere ved at fjerne alle referencer til x i funktionsdefinitioner, da x nu er en gratis variabel og tilgængelig for dem alle. Her er vores nye definition af 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);}

De eneste referencer til parameter x er i beregninger, hvor x's værdi er nødvendig. Lad os indlæse denne nye definition i skallen og teste den:

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

Lexikalisk scoping og blokstruktur er vigtige funktioner i JavaScript og giver os mulighed for at konstruere programmer, der er lettere at forstå og administrere. Dette er især vigtigt, når vi begynder at konstruere større og mere komplekse programmer.