شرح مفهوم Scope في جافا سكريبت

يعتبر مفهوم Scope أو النطاق من المفاهيم الأساسية في لغة البرمجة جافا سكريبت والذي يجب على كل المطورين بهذه اللغة أن يفهموه ويستوعبوه بشكل جيد.

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

ما هو Scope ؟

في كل لغات البرمجة، عندما ننوي إنشاء متغير جديد فإننا نريده أن يكون متاحا ومتوفرا في نطاق محدد حيث نحتاج استخدامه. هذا النطاق قد يكون عبارة عن دالة في أحيان كثيرة وذلك بإنشاء المتغير باستعمال واحدة من الكلمات المفتاحية المتاحة: var ،let أو const.

يمكن لهذا النطاق أن يكون في أحيان أخرى عبارة عن أي Code Block. ويعنى به أي مكان بين معقوفتين { ... } مثل أن يكون المتغير معرفا وسط تعبير شرطي if، حلقة for أو while أو حتى داخل switch، والشرط في هذه الحالة أن يتم تعريف المتغير باستعمال إما let أو const. إذا تم تعريفه بواسطة var فإنه سيصبح متاحا كذلك خارج دائرة ذلك ال Code Block (مثلا الدالة الحاوية أو Parent Scope).

القابلية للوصول إلى المتغيرات واستخدامها في جافا سكريبت تدار بواسطة ميزة ال Scope. لا يمكننا الوصول إلى أي متغير إلا في المكان أو المجال الذي ينتمي له.

مثال

function getCartTotal(cart) {
  // function scope
  let total = cart.products
    .map((product) => product.price)
    .reduce((accum, current) => accum + current, 0);

  return total;
}
getCartTotal(someCart);
console.log(total); // ReferenceError: total is not defined

بإمكاننا الوصول إلى المتغير total واستخدامه كما يحلو لنا داخل النطاق الذي ينتمي له وهو في هذه الحالة الدالة getCartTotal. بينما لا يمكننا استخدامه أبدا خارج ذلك النطاق لأننا سنحصل على خطأ جراء ذلك ReferenceError: total is not defined.

ماذا عن النطاقات المتداخلة أو Nested Scopes ؟

كلنا نعرف بأنه في لغة جافا سكريبت يمكننا معاملة الدوال كما تتم معاملة أية أنواع أخرى من القيم والكائنات. مثلا يمكننا تعريف دالة داخل دالة، وهذا شائع جدا في برامج وأكواد جافا سكريبت.

function parentFunction() {
  const a = 'parent';

  function childFunction() {
    console.log(a);
  }

  childFunction();
}

parentFunction(); // parent

تلاحظون أنه رغم أننا قمنا بتعريف المتغير a في الدالة parentFunction، غير أننا تمكننا من الوصول إليه أيضا داخل الدالة البنت childFunction. ذلك أن الأخيرة موجودة في نطاق parentFunction.

بتعبير آخر: المتغيرات التي يمكن الوصول إليها داخل نطاق A يمكن الوصول إليها كذلك داخل النطاقات الموجودة تحته والمنحذرة منه.

كل متغير تتم مصادفته واستعماله داخل نطاق معين يتم تتبع مصدره عن طريق البحث في ذات Scope أولا، بعد ذلك يتم الصعود إلى النطاق الأب Parent Scope وهكذا دواليك حتى يتم الوصول إلى النطاق العام أو Global Scope. إذا تم العثور على مصدره فإن البرنامج يواصل الإشتغال بنجاح، وإذا لم يتم العثور على أي مكان تم فيه التصريح به فإن مترجم جافا سكريبت يقوم برمي خطأ في أوجهنا مثل الذي رأيناه أعلاه ReferenceError.

الأمر أشبه بالصعود في من الطابق السفلي في عمارة نحو الطوابق العلوية بحثا عن شيء معين، وتعرف هذه الحالة باسم Scope chain.

Lexical Scope

يمكن كذلك أن نسمع أو نرى أحدهم يتكلم عن شيء اسمه Lexical Scope. هذا الأخير ببساطة هو النطاق A الذي يحيط بنطاق B. حيث أنه داخل النطاق B يمكننا الوصول إلى جميع المتغيرات المنتمية للنطاق الأب A.

نقول أن النطاق A هو بمثابة Lexical Scope للنطاق B. وإذا كان هناك نطاق آخر C منحذر من B فإننا نقول بأن Lexical Scope للنطاق C هما النطاقان B و A وهكذا دواليك مهما كان عمق Nested Scopes.

في المثال السابق نقول بأن Lexical Scope للدالة childFunction هو نطاق الدالة parentFunction والنطاق العام أي Global Scope (في المتصفح هو الكائن window).

يمكنك دعمنا عبر منصة باتريون لكي نستطيع تسخير وقت أكبر في نشر وتجديد المحتوى.
الدعم سيساعدنا كذلك على تطوير ذلك المحتوى وتحسينه.

ادعمنا على باتريون