Eine einfache Einführung in Lexical Scoping in JavaScript

Lexikalisches Scoping ist ein Thema, das viele Programmierer erschreckt. Eine der besten Erklärungen für das lexikalische Scoping findet sich in Kyle Simpsons Buch You Don't Know JS: Scope and Closures. Es fehlt jedoch auch seine Erklärung, weil er kein wirkliches Beispiel verwendet.

Eines der besten Beispiele dafür, wie lexikalisches Scoping funktioniert und warum es wichtig ist, findet sich im berühmten Lehrbuch „Die Struktur und Interpretation von Computerprogrammen“ (SICP) von Harold Abelson und Gerald Jay Sussman. Hier ist ein Link zu einer PDF-Version des Buches: SICP.

SICP verwendet Scheme, einen Dialekt von Lisp, und gilt als einer der besten einführenden Texte der Informatik, die jemals geschrieben wurden. In diesem Artikel möchte ich noch einmal auf das Beispiel des lexikalischen Scoping mit JavaScript als Programmiersprache eingehen.

Unser Beispiel

Das verwendete Beispiel von Abelson und Sussman ist die Berechnung von Quadratwurzeln nach der Newtonschen Methode. Die Newtonsche Methode ermittelt aufeinanderfolgende Näherungen für die Quadratwurzel einer Zahl, bis die Näherung innerhalb einer Toleranzgrenze liegt, die akzeptabel ist. Lassen Sie uns ein Beispiel durcharbeiten, wie es Abelson und Sussman in SICP tun.

Das Beispiel, das sie verwenden, ist das Finden der Quadratwurzel von 2. Sie beginnen mit einer Schätzung der Quadratwurzel von 2, sagen wir 1. Sie verbessern diese Schätzung, indem Sie die ursprüngliche Zahl durch die Schätzung dividieren und dann diesen Quotienten und die aktuelle Schätzung zu mitteln Überlegen Sie sich die nächste Vermutung. Sie hören auf, wenn Sie ein akzeptables Maß an Annäherung erreicht haben. Abelson und Sussman verwenden den Wert 0,001. Hier ist ein Durchlauf der ersten Schritte in der Berechnung:

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

Und so weiter, bis die Schätzung innerhalb unserer Näherungsgrenze liegt, die für diesen Algorithmus 0,001 beträgt.

Eine JavaScript-Funktion für die Newtonsche Methode

Nach dieser Demonstration der Methode beschreiben die Autoren ein allgemeines Verfahren zur Lösung dieses Problems in Schema. Anstatt Ihnen den Schema-Code zu zeigen, schreibe ich ihn in JavaScript:

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

Als nächstes müssen wir einige andere Funktionen ausarbeiten, einschließlich isGoodEnough () und Improvement (), zusammen mit einigen anderen Hilfsfunktionen. Wir beginnen mit verbessern (). Hier ist die Definition:

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

Diese Funktion verwendet einen Hilfsfunktionsdurchschnitt (). Hier ist diese Definition:

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

Jetzt können wir die Funktion isGoodEnough () definieren. Diese Funktion dient dazu zu bestimmen, wann unsere Vermutung nahe genug an unserer Approximationstoleranz (0,001) liegt. Hier ist die Definition von isGoodEnough ():

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

Diese Funktion verwendet eine square () -Funktion, die einfach zu definieren ist:

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

Jetzt brauchen wir nur noch eine Funktion, um die Dinge in Gang zu bringen:

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

Diese Funktion verwendet 1.0 als Startschätzung, was normalerweise in Ordnung ist.

Jetzt können wir unsere Funktionen testen, um festzustellen, ob sie funktionieren. Wir laden sie in eine JS-Shell und berechnen dann einige Quadratwurzeln:

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

Die Funktionen scheinen gut zu funktionieren. Hier lauert jedoch eine bessere Idee. Diese Funktionen werden alle unabhängig voneinander geschrieben, obwohl sie zusammen arbeiten sollen. Wir werden die Funktion isGoodEnough () wahrscheinlich nicht mit anderen Funktionen oder allein verwenden. Die einzige Funktion, die für den Benutzer von Bedeutung ist, ist die Funktion sqrt (), da diese aufgerufen wird, um eine Quadratwurzel zu finden.

Block Scoping verbirgt Hilfsfunktionen

Die Lösung besteht darin, das Block-Scoping zu verwenden, um alle erforderlichen Hilfsfunktionen innerhalb des Blocks der Funktion sqrt () zu definieren. Wir werden Quadrat () und Durchschnitt () aus der Definition entfernen, da diese Funktionen in anderen Funktionsdefinitionen nützlich sein können und nicht so beschränkt auf die Verwendung in einem Algorithmus sind, der die Newtonsche Methode implementiert. Hier ist die Definition der Funktion sqrt () mit den anderen Hilfsfunktionen, die im Rahmen von sqrt () definiert wurden:

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

Wir können dieses Programm jetzt in unsere Shell laden und einige Quadratwurzeln berechnen:

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

Beachten Sie, dass Sie keine der Hilfsfunktionen von außerhalb der Funktion sqrt () aufrufen können:

> 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 die Definitionen dieser Funktionen (verbessern () und isGoodEnough ()) in den Bereich von sqrt () verschoben wurden, kann auf einer höheren Ebene nicht auf sie zugegriffen werden. Natürlich können Sie jede der Hilfsfunktionsdefinitionen außerhalb der Funktion sqrt () verschieben, um global auf sie zuzugreifen, wie wir es mit durchschnitt () und square () getan haben.

Wir haben unsere Implementierung der Newtonschen Methode erheblich verbessert, aber wir können noch etwas tun, um unsere sqrt () - Funktion zu verbessern, indem wir sie noch weiter vereinfachen, indem wir den lexikalischen Umfang nutzen.

Verbesserung der Klarheit mit Lexical Scope

Das Konzept hinter dem lexikalischen Bereich besteht darin, dass andere Prozeduren (Funktionen), die in dieser Umgebung definiert sind, Zugriff auf den Wert dieser Variablen haben, wenn eine Variable an eine Umgebung gebunden ist. Dies bedeutet, dass in der Funktion sqrt () der Parameter x an diese Funktion gebunden ist, was bedeutet, dass jede andere im Hauptteil von sqrt () definierte Funktion auf x zugreifen kann.

Wenn wir dies wissen, können wir die Definition von sqrt () noch weiter vereinfachen, indem wir alle Verweise auf x in Funktionsdefinitionen entfernen, da x jetzt eine freie Variable ist und für alle zugänglich ist. Hier ist unsere neue Definition von 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);}

Die einzigen Verweise auf Parameter x beziehen sich auf Berechnungen, bei denen der Wert von x benötigt wird. Laden wir diese neue Definition in die Shell und testen sie:

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