ثلاث كلمات مفتاحية لمهمة واحدة
في جافا سكريبت، هناك ثلاث طرق للتصريح عن المتغيرات: var و let و const. جميعها تربط اسمًا بقيمة، لكنها تختلف في النطاق (scope) و قواعد إعادة الإسناد وسلوكها قبل تنفيذ سطر التصريح. الكود الحديث يعتمد على const في معظم الأحيان، ويستخدم let حين تحتاج القيمة إلى التغيّر، أمّا var فنادرًا ما تُستخدم اليوم.
باختصار:
بقية هذه الصفحة تشرح السبب وراء سلوك هذه الأسطر الثلاثة بهذا الشكل.
const: الخيار الافتراضي
يُنشئ const ارتباطًا لا يمكن إعادة إسناد قيمة له. بمجرد أن تكتب const x = 5، لن تستطيع لاحقًا كتابة x = 6، إذ سيرمي المحرك خطأ من نوع TypeError.
هذه هي القاعدة باختصار. بما أن معظم المتغيرات في أي برنامج مكتوب بشكل جيد لا تحتاج إلى إعادة إسناد، فإن const يغطي الغالبية العظمى من الحالات. والبدء دائمًا بـ const يجعل المواضع التي تتغير فيها القيمة فعلًا تبرز بوضوح.
من أكثر نقاط اللبس شيوعًا: const يحمي الارتباط (binding) وليس القيمة نفسها. فإذا كانت القيمة كائنًا أو مصفوفة، فمحتوياتها تبقى قابلة للتعديل:
const تعني أن "هذا الاسم يشير دائمًا إلى هذا الكائن"، ولا تعني أن "هذا الكائن لن يتغيّر أبدًا". فإذا أردت تجميد الكائن نفسه فعليًا، فالأداة المناسبة هي Object.freeze(user). لكن في الواقع العملي، يكتفي معظم المطوّرين بالاتفاق الضمني على أن كائنات const لا يجب تعديلها.
متى نستخدم let في جافا سكريبت؟
let مطابقة لـ const في كل شيء، باستثناء أنها تسمح لك بإعادة تعيين القيمة. استخدمها مع العدّادات، والمُجمِّعات، ومتغيّرات الحلقات، وفي أي مكان تتغيّر فيه القيمة فعليًا مع مرور الوقت.
إذا وجدت نفسك تكتب let دون أن تعيد إسناد قيمة للمتغير أبداً، فاستبدله بـ const. معظم المشاريع فيها linter سينبهك إلى ذلك تلقائياً.
نطاق الكتلة (Block Scope) في جافا سكريبت
كل من let و const لهما نطاق كتلة، أي أن صلاحيتهما محصورة داخل الكتلة التي صُرِّح عنهما فيها. والكتلة هي أي شيء بين { و } — سواء كان جسم جملة if، أو حلقة for، أو دالة، أو حتى كتلة مستقلة مجردة على شكل { ... }. أي متغير تُصرّح عنه داخل الكتلة لن يكون له وجود خارجها.
هذا هو السلوك الذي تريده فعلاً: كل متغير يبقى محصوراً في نطاق المهمة التي يخصها، ويصبح تعارض الأسماء عن طريق الخطأ أمراً مستحيلاً.
أما var فلا يفعل ذلك، وهذا هو السبب الرئيسي لتجنّبه.
var ونطاق الدالة: مفاجآت غير سارّة
المتغير المُصرَّح عنه بـ var يرتبط بنطاق أقرب دالة، لا بنطاق أقرب بلوك. ومعنى ذلك أن var المُعرَّف داخل if أو for يتسرّب إلى خارجه ليصبح متاحاً في الدالة المحيطة بأكملها:
ذلك التساهل في تحديد النطاق (scope) هو مصدر قائمة طويلة من الأخطاء الكلاسيكية في جافا سكريبت — وأشهرها تلك الحلقات التي تتشارك فيها جميع التكرارات نفس var i، فتنتهي كل الـ callbacks وهي ترى القيمة الأخيرة فقط.
كذلك يسمح لك var بإعادة التصريح عن نفس الاسم في نفس النطاق دون أي اعتراض، وهذا يُخفي الأخطاء الإملائية:
الكود الحديث يعتمد على let و const بشكل شبه كامل. أما var فلن تصادفه إلا في أكواد قديمة، أو إجابات قديمة على Stack Overflow، أو سكربتات تحتاج للعمل في بيئات قديمة جدًا.
الرفع (Hoisting) والمنطقة الميتة المؤقتة (Temporal Dead Zone)
التصريحات الثلاثة جميعها تخضع لـ الرفع (hoisting) — بمعنى أن محرك جافا سكريبت يعرف بوجودها قبل تنفيذ الكود داخل الكتلة — لكن سلوك كل منها يختلف قبل الوصول إلى سطر التصريح فعليًا.
المتغير المُعرَّف بـ var يُرفع ويُهيَّأ تلقائيًا بقيمة undefined، لذلك يمكنك الإشارة إليه قبل سطر var دون أن يظهر أي خطأ:
كلٌّ من let و const يخضع لعمليّة hoisting، لكن بدون تهيئة. أيّ محاولة للوصول إليهما قبل سطر التصريح ترمي خطأً. هذه الفترة الممتدّة من لحظة الدخول إلى النطاق وحتى تنفيذ سطر التصريح تُعرف بـ المنطقة الميتة الزمنيّة (temporal dead zone أو TDZ):
منطقة TDZ ميزة مقصودة، مش عيب. هي اللي بتحوّل خطأ "استخدام المتغير قبل التصريح به" من قيمة undefined صامتة إلى خطأ صريح بيصرخ في وشّك، وده بيمسك كتير من الأخطاء الإملائية ومشاكل ترتيب الكود.
استخدام const مع حلقة for...of
حالة بسيطة لكنها شائعة: حلقة for...of بتنشئ ربطًا (binding) جديدًا في كل تكرار، فتقدر تستخدم const لمتغير الحلقة حتى لو بدا إنه "بيتغير" من تكرار للتاني.
كل دورة تحصل على ربط (binding) خاص بها للمتغير name، أي أنه لا يوجد متغير واحد تُعاد قيمته في كل مرة. أما حلقة for (let i = 0; i < n; i++) التقليدية فلا تزال تحتاج إلى let، لأن i هنا ربط واحد تُزاد قيمته في كل تكرار.
قاعدة عملية للتصريح عن المتغيرات
اختر طريقة التصريح وفق هذا الترتيب من الأفضلية:
constافتراضيًا. إذا كانت القيمة لن يُعاد إسنادها، فأفصِح عن ذلك.letحين يحتاج الربط فعلًا إلى التغيير.varفقط عند العمل على مشروع يفرض استخدامها.
الالتزام بهذه القاعدة يجعل نية كل متغير واضحة من النظرة الأولى: هل قيمته قابلة لإعادة الإسناد أم لا؟ وهل نطاقه محصور في هذه الكتلة أم في الدالة بأكملها؟
MAX_RETRIES و users لا تتغير أبدًا، فاستخدمنا const. أما successful فقيمتها تكبر مع كل عملية ناجحة، ولهذا استخدمنا let. وuser يُنشَأ من جديد في كل دورة من الحلقة، فـ const هي الخيار المناسب. بمجرد قراءة الكود من أعلى إلى أسفل تعرف أي القيم تتبدّل وأيها ثابت، دون الحاجة لتشغيله.
الخطوة التالية: الأنواع الأولية (Primitive Types)
بعد أن أتقنت طريقة التصريح عن المتغيرات في JavaScript، يبقى سؤال مهم: ما هي أنواع القيم التي يمكن لهذه المتغيرات أن تحملها؟ تمتلك JavaScript مجموعة صغيرة من الأنواع الأولية — الأرقام، السلاسل النصية، القيم المنطقية، وبضعة أنواع أخرى — ولكل منها خصائصه وطباعه الخاصة. وهذا ما سنتناوله في الصفحة التالية.
الأسئلة الشائعة
ما الفرق بين let و const و var في JavaScript؟
كلٌّ من const و let ينتمي إلى نطاق الكتلة (block scope) وقد أُضيفا في ES2015، بينما var أقدم منهما ويتبع نطاق الدالة (function scope). لا يمكن إعادة تعيين قيمة const بعد تعريفها، أما let فيسمح بذلك. كذلك يُرفع var ويُهيَّأ تلقائيًا بالقيمة undefined، في حين يُرفع let و const لكن لا يمكن استخدامهما قبل سطر التعريف الفعلي، وهذا ما يُعرف بالمنطقة الميتة المؤقتة (Temporal Dead Zone).
هل تجعل const القيمة غير قابلة للتعديل في JavaScript؟
لا. const تمنع فقط إعادة إسناد قيمة جديدة للمتغير نفسه، لكنها لا تمنع تعديل محتوى القيمة. لو كانت القيمة كائنًا أو مصفوفة فيمكنك تعديل خصائصها بحرية؛ فمثلًا const user = {}; user.name = 'Ada' يعمل بدون أي خطأ. إذا أردت ثباتًا حقيقيًا فاستخدم Object.freeze أو مكتبة مخصصة لذلك.
هل ما زال استخدام var مناسبًا في JavaScript الحديثة؟
نادرًا جدًا. يوفر كلٌّ من let و const قواعد نطاق أوضح ويكشفان الأخطاء مبكرًا أثناء التحليل. غالبًا لن تصادف var إلا في الأكواد القديمة أو في بعض السكربتات التي يجب أن تعمل في بيئات لا تدعم ES2015.