مقدمة سهلة لـ Lexical Scoping في JavaScript

النطاق المعجمي هو موضوع يخيف العديد من المبرمجين. يمكن العثور على أحد أفضل تفسيرات تحديد النطاق المعجمي في كتاب Kyle Simpson You Don't Know JS: Scope and Closures. ومع ذلك ، حتى تفسيره غير موجود لأنه لا يستخدم مثالًا حقيقيًا.

يمكن العثور على أحد أفضل الأمثلة الحقيقية لكيفية عمل تحديد النطاق المعجمي ، وسبب أهميته ، في الكتاب المدرسي الشهير ، "هيكل وتفسير برامج الكمبيوتر" (SICP) لهارولد أبيلسون وجيرالد جاي سوسمان. هنا رابط لنسخة PDF من الكتاب: SICP.

يستخدم SICP Scheme ، إحدى لهجات Lisp ، ويعتبر أحد أفضل النصوص التمهيدية لعلوم الكمبيوتر المكتوبة على الإطلاق. في هذه المقالة ، أود إعادة النظر في مثالهم على النطاق المعجمي باستخدام JavaScript كلغة برمجة.

مثالنا

المثال الذي استخدمه أبيلسون وسوسمان هو حساب الجذور التربيعية باستخدام طريقة نيوتن. تعمل طريقة نيوتن من خلال تحديد التقديرات المتتالية للجذر التربيعي لرقم حتى يأتي التقريب ضمن حد التفاوت المسموح به. دعونا نعمل من خلال مثال ، كما يفعل Abelson و Sussman في SICP.

المثال الذي يستخدمونه هو إيجاد الجذر التربيعي للرقم 2. تبدأ بالتخمين عند الجذر التربيعي للرقم 2 ، لنقل 1. يمكنك تحسين هذا التخمين بقسمة الرقم الأصلي على التخمين ثم حساب متوسط ​​حاصل القسمة والتخمين الحالي على طرح التخمين التالي. تتوقف عندما تصل إلى مستوى مقبول من التقريب. يستخدم أبيلسون وسوسمان القيمة 0.001. فيما يلي نظرة عامة على الخطوات القليلة الأولى في الحساب:

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

وهكذا حتى يأتي التخمين ضمن حدود التقريب لدينا ، والتي بالنسبة لهذه الخوارزمية هي 0.001.

وظيفة JavaScript لطريقة نيوتن

بعد هذا العرض التوضيحي للطريقة ، يصف المؤلفون إجراءً عامًا لحل هذه المشكلة في المخطط. بدلاً من إظهار رمز المخطط لك ، سأكتبه في JavaScript:

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

بعد ذلك ، نحتاج إلى توضيح عدة وظائف أخرى ، بما في ذلك isGoodEnough () و Improvement () ، إلى جانب بعض الوظائف المساعدة الأخرى. سنبدأ بالتحسين (). هنا التعريف:

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

تستخدم هذه الوظيفة متوسط ​​الدالة المساعدة (). هذا هو التعريف:

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

نحن الآن جاهزون لتحديد دالة isGoodEnough (). تعمل هذه الوظيفة على تحديد متى يكون تخميننا قريبًا بدرجة كافية من تحمل التقريب (0.001). هنا تعريف isGoodEnough ():

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

تستخدم هذه الوظيفة دالة مربعة () ، يسهل تحديدها:

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

الآن كل ما نحتاجه هو وظيفة لبدء الأمور:

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

تستخدم هذه الوظيفة 1.0 كتخمين بداية ، وهو عادة ما يكون جيدًا.

نحن الآن جاهزون لاختبار وظائفنا لمعرفة ما إذا كانت تعمل. نقوم بتحميلها في غلاف JS ثم نحسب بعض الجذور التربيعية:

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

يبدو أن الوظائف تعمل بشكل جيد. ومع ذلك ، هناك فكرة أفضل كامنة هنا. تتم كتابة جميع هذه الوظائف بشكل مستقل ، على الرغم من أنها تهدف إلى العمل مع بعضها البعض. ربما لن نستخدم وظيفة isGoodEnough () مع أي مجموعة أخرى من الوظائف ، أو منفردة. أيضًا ، الوظيفة الوحيدة التي تهم المستخدم هي الوظيفة sqrt () ، حيث يتم استدعاؤها لإيجاد الجذر التربيعي.

وظائف مساعد يخفي تحديد النطاق

الحل هنا هو استخدام نطاق الكتلة لتحديد جميع الوظائف المساعدة الضرورية داخل كتلة دالة sqrt (). سنقوم بإزالة المربع () والمتوسط ​​() من التعريف ، حيث قد تكون هذه الوظائف مفيدة في تعريفات الوظائف الأخرى ولا تقتصر على الاستخدام في خوارزمية تنفذ طريقة نيوتن. فيما يلي تعريف الدالة sqrt () الآن مع وظائف المساعد الأخرى المحددة في نطاق 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);}

يمكننا الآن تحميل هذا البرنامج في غلافنا وحساب بعض الجذور التربيعية:

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

لاحظ أنه لا يمكنك استدعاء أي من وظائف المساعد من خارج دالة 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

نظرًا لأنه تم نقل تعريفات هذه الوظائف (تحسين () و isGoodEnough ()) داخل نطاق sqrt () ، فلا يمكن الوصول إليها على مستوى أعلى. بالطبع ، يمكنك نقل أي من تعريفات الدالة المساعدة خارج الدالة sqrt () للوصول إليها عالميًا كما فعلنا مع المتوسط ​​() والمربع ().

لقد قمنا بتحسين تنفيذنا لطريقة نيوتن بشكل كبير ولكن لا يزال هناك شيء آخر يمكننا القيام به لتحسين وظيفة sqrt () الخاصة بنا من خلال تبسيطها أكثر من خلال الاستفادة من النطاق المعجمي.

تحسين الوضوح باستخدام النطاق المعجمي

المفهوم الكامن وراء النطاق المعجمي هو أنه عندما يكون المتغير مرتبطًا ببيئة ، فإن الإجراءات (الوظائف) الأخرى المحددة في تلك البيئة لها حق الوصول إلى قيمة ذلك المتغير. هذا يعني أنه في دالة sqrt () ، ترتبط المعلمة x بهذه الوظيفة ، مما يعني أن أي دالة أخرى محددة داخل جسم sqrt () يمكنها الوصول إلى x.

بمعرفة هذا ، يمكننا تبسيط تعريف sqrt () أكثر عن طريق إزالة جميع المراجع إلى x في تعريفات الوظائف لأن x أصبح الآن متغيرًا مجانيًا ويمكن الوصول إليه جميعًا. فيما يلي تعريفنا الجديد لـ 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);}

المراجع الوحيدة للمعامل x موجودة في الحسابات حيث تكون قيمة x مطلوبة. لنقم بتحميل هذا التعريف الجديد في الغلاف ونختبره:

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

يعد النطاق المعجمي وبنية الكتل من الميزات المهمة في JavaScript ويسمح لنا بإنشاء برامج يسهل فهمها وإدارتها. هذا مهم بشكل خاص عندما نبدأ في إنشاء برامج أكبر وأكثر تعقيدًا.