Var و Let و Const: مقارنة شاملة لإعلان المتغيرات في JavaScript
منذ إطلاق ES2015 (المعروفة أيضاً بـ ES6)، شهدت لغة JavaScript إضافة العديد من الميزات الجديدة والمثيرة. ومع مرور الوقت، أصبح من المتوقع أن يكون معظم مطوري JavaScript قد ألفوا هذه الميزات وبدأوا في استخدامها. ومع أن هذا الافتراض قد يكون صحيحاً جزئياً، إلا أن بعض هذه الميزات قد تظل غامضة لعدد من المطورين.
إحدى الميزات البارزة التي جاءت مع ES6 هي إضافة الكلمتين المفتاحيتين let و const، اللتين تُستخدمان لإعلان المتغيرات. السؤال الذي يطرح نفسه هو: ما الذي يميزهما عن الكلمة المفتاحية القديمة var التي اعتدنا استخدامها؟ إذا كنت لا تزال غير متأكد من الإجابة، فهذا المقال موجه لك.
في هذا المقال، سنتعمق في مناقشة var و let و const من حيث نطاقها (Scope)، وكيفية استخدامها، ومفهوم الرفع (Hoisting). خلال قراءتك، ركز على الفروقات الجوهرية التي سنسلط الضوء عليها، والتي ستساعدك على كتابة كود JavaScript أكثر كفاءة وخالٍ من الأخطاء.
فهم الكلمة المفتاحية var: الأساسيات والتحديات
قبل ظهور ES6، كانت إعلانات المتغيرات باستخدام var هي القاعدة السائدة. ومع ذلك، ارتبطت بهذه الطريقة بعض المشكلات، مما استدعى ظهور طرق جديدة لإعلان المتغيرات. دعنا أولاً نفهم var بشكل أفضل قبل أن نناقش تلك المشكلات.
نطاق var (Scope)
يشير النطاق (Scope) بشكل أساسي إلى الأماكن التي تكون فيها هذه المتغيرات متاحة للاستخدام. إعلانات var تكون إما ذات نطاق عام (globally scoped) أو ذات نطاق دالة/محلي (function/locally scoped).
- نطاق عام: يكون النطاق عاماً عندما يتم إعلان متغير
varخارج أي دالة. هذا يعني أن أي متغير يتم إعلانه باستخدامvarخارج كتلة دالة يكون متاحاً للاستخدام في النافذة بأكملها (window). - نطاق دالة: يكون
varذا نطاق دالة عندما يتم إعلانه داخل دالة. هذا يعني أنه يكون متاحاً ويمكن الوصول إليه فقط داخل تلك الدالة.
لفهم أعمق، انظر إلى المثال التالي:
var greeter = "hey hi";
function newFunction() {
var hello = "hello";
}
هنا، المتغير greeter ذو نطاق عام لأنه موجود خارج أي دالة، بينما المتغير hello ذو نطاق دالة. لذلك، لا يمكننا الوصول إلى المتغير hello خارج الدالة. إذا حاولنا القيام بذلك:
var tester = "hey hi";
function newFunction() {
var hello = "hello";
}
console.log(hello); // error: hello is not defined
سنحصل على خطأ نتيجة لعدم توفر المتغير hello خارج الدالة.
إعادة الإعلان والتحديث لمتغيرات var
يمكن إعادة إعلان وتحديث المتغيرات المعلنة باستخدام var. هذا يعني أنه يمكننا القيام بذلك ضمن نفس النطاق ولن نحصل على أي خطأ:
var greeter = "hey hi";
var greeter = "say Hello instead";
وكذلك هذا:
var greeter = "hey hi";
greeter = "say Hello instead";
مفهوم الرفع (Hoisting) لـ var
الرفع (Hoisting) هو آلية في JavaScript يتم فيها نقل إعلانات المتغيرات والدوال إلى أعلى نطاقها قبل تنفيذ الكود. هذا يعني أنه إذا قمنا بما يلي:
console.log(greeter);
var greeter = "say hello";
فإنه يتم تفسيره على النحو التالي:
var greeter;
console.log(greeter); // greeter is undefined
greeter = "say hello";
لذا، يتم رفع متغيرات var إلى أعلى نطاقها ويتم تهيئتها بقيمة undefined.
مشكلة Var: إعادة التعريف غير المقصودة
توجد نقطة ضعف تأتي مع استخدام var. سأستخدم المثال أدناه لتوضيح ذلك:
var greeter = "hey hi";
var times = 4;
if (times > 3) {
var greeter = "say Hello instead";
}
console.log(greeter); // "say Hello instead"
في هذا المثال، بما أن الشرط times > 3 يعود بالقيمة true، يتم إعادة تعريف المتغير greeter ليصبح "say Hello instead". بينما لا تمثل هذه مشكلة إذا كنت ترغب عن قصد في إعادة تعريف greeter، فإنها تصبح مشكلة حقيقية عندما لا تدرك أن متغيراً باسم greeter قد تم تعريفه مسبقاً.
إذا كنت قد استخدمت greeter في أجزاء أخرى من التعليمات البرمجية الخاصة بك، فقد تتفاجأ بالناتج الذي تحصل عليه. من المرجح أن يتسبب هذا في الكثير من الأخطاء (bugs) في الكود الخاص بك. ولهذا السبب، أصبحت let و const ضروريتين.
الكشف عن Let: بديل Var المحسّن
أصبحت الكلمة المفتاحية let هي المفضلة الآن لإعلان المتغيرات. وليس من المستغرب ذلك، فهي تأتي كتحسين لإعلانات var وتحل المشكلة التي ناقشناها للتو مع var. دعنا نرى لماذا.
نطاق الكتلة (Block Scope) لـ let
الكتلة (Block) هي جزء من التعليمات البرمجية محاطة بأقواس متعرجة {}. أي شيء داخل الأقواس المتعرجة يعتبر كتلة. لذلك، فإن المتغير المُعلن داخل كتلة باستخدام let يكون متاحاً للاستخدام فقط داخل تلك الكتلة. دعني أوضح هذا بمثال:
let greeting = "say Hi";
let times = 4;
if (times > 3) {
let hello = "say Hello instead";
console.log(hello); // "say Hello instead"
}
console.log(hello); // hello is not defined
نلاحظ أن استخدام المتغير hello خارج كتلته (الأقواس المتعرجة التي تم تعريفه فيها) يعيد خطأ. هذا لأن متغيرات let ذات نطاق كتلة (block scoped).
تحديث let وإعادة إعلانه
يمكن تحديث متغيرات let ولكن لا يمكن إعادة إعلانها. تماماً مثل var، يمكن تحديث متغير مُعلن باستخدام let ضمن نطاقه. ولكن على عكس var، لا يمكن إعادة إعلان متغير let ضمن نطاقه. لذلك، بينما سيعمل هذا:
let greeting = "say Hi";
greeting = "say Hello instead";
فإن هذا سيعيد خطأ:
let greeting = "say Hi";
let greeting = "say Hello instead"; // error: Identifier 'greeting' has already been declared
ومع ذلك، إذا تم تعريف نفس المتغير في نطاقات مختلفة، فلن يكون هناك خطأ:
let greeting = "say Hi";
if (true) {
let greeting = "say Hello instead";
console.log(greeting); // "say Hello instead"
}
console.log(greeting); // "say Hi"
لماذا لا يوجد خطأ؟ هذا لأن كلتا الحالتين تُعاملان كمتغيرات مختلفة نظراً لاختلاف نطاقاتهما. هذه الحقيقة تجعل let خياراً أفضل من var. عند استخدام let، لا داعي للقلق إذا كنت قد استخدمت اسماً لمتغير من قبل، حيث أن المتغير موجود فقط ضمن نطاقه. أيضاً، بما أنه لا يمكن إعلان المتغير أكثر من مرة ضمن نطاق واحد، فإن المشكلة التي نوقشت سابقاً والتي تحدث مع var لا تحدث.
مفهوم الرفع (Hoisting) لـ let
تماماً مثل var، يتم رفع إعلانات let إلى الأعلى. ولكن على عكس var الذي يتم تهيئته كـ undefined، فإن الكلمة المفتاحية let لا يتم تهيئتها. لذلك، إذا حاولت استخدام متغير let قبل إعلانه، ستحصل على خطأ ReferenceError.
Const: المتغيرات الثابتة وغير القابلة للتغيير
المتغيرات المعلنة باستخدام const تحافظ على قيم ثابتة. وتشترك إعلانات const في بعض أوجه التشابه مع إعلانات let.
نطاق الكتلة (Block Scope) لـ const
مثل إعلانات let، لا يمكن الوصول إلى إعلانات const إلا داخل الكتلة التي تم إعلانها فيها.
عدم إمكانية تحديث أو إعادة إعلان const
لا يمكن تحديث أو إعادة إعلان const. هذا يعني أن قيمة المتغير المعلن باستخدام const تظل كما هي ضمن نطاقه. لا يمكن تحديثه أو إعادة إعلانه. لذلك، إذا أعلنا متغيراً باستخدام const، فلا يمكننا القيام بذلك:
const greeting = "say Hi";
greeting = "say Hello instead"; // error: Assignment to constant variable.
ولا هذا:
const greeting = "say Hi";
const greeting = "say Hello instead"; // error: Identifier 'greeting' has already been declared
يجب تهيئة كل إعلان const، بالتالي، في وقت الإعلان. هذا السلوك يختلف نوعاً ما عندما يتعلق الأمر بالكائنات (objects) المعلنة باستخدام const. بينما لا يمكن تحديث كائن const (أي إعادة تعيين الكائن نفسه لمتغير آخر)، يمكن تحديث خصائص هذا الكائن. لذلك، إذا أعلنا كائناً const على النحو التالي:
const greeting = {
message: "say Hi",
times: 4
};
بينما لا يمكننا القيام بذلك:
greeting = {
words: "Hello",
number: "five"
}; // error: Assignment to constant variable.
يمكننا القيام بذلك:
greeting.message = "say Hello instead";
سيؤدي هذا إلى تحديث قيمة greeting.message دون إرجاع أخطاء.
مفهوم الرفع (Hoisting) لـ const
تماماً مثل let، يتم رفع إعلانات const إلى الأعلى ولكن لا يتم تهيئتها. لذلك، إذا حاولت استخدام متغير const قبل إعلانه، ستحصل على خطأ ReferenceError.
مقارنة سريعة: الفروقات الجوهرية بين Var و Let و Const
في حال فاتتك الفروقات، إليك ملخص سريع لها:
- إعلانات
varذات نطاق عام أو نطاق دالة، بينماletوconstذات نطاق كتلة. - يمكن تحديث وإعادة إعلان متغيرات
varضمن نطاقها. - يمكن تحديث متغيرات
letولكن لا يمكن إعادة إعلانها ضمن نطاقها. - لا يمكن تحديث أو إعادة إعلان متغيرات
constضمن نطاقها (مع استثناء خصائص الكائنات). - جميعها يتم رفعها (hoisted) إلى أعلى نطاقها. ولكن بينما يتم تهيئة متغيرات
varبقيمةundefined، فإن متغيراتletوconstلا يتم تهيئتها، مما يؤدي إلىReferenceErrorعند محاولة الوصول إليها قبل الإعلان. - يمكن إعلان
varوletدون تهيئتها، بينما يجب تهيئةconstأثناء الإعلان.
الخلاصة التقنية
في الختام، يمثل فهم الفروقات بين var و let و const حجر الزاوية في كتابة كود JavaScript حديث وفعال وخالٍ من الأخطاء. بينما لا يزال var جزءاً من اللغة، فإن مشكلاته المتعلقة بالنطاق العام وإعادة التعريف غير المقصودة تجعله خياراً أقل تفضيلاً في المشاريع الجديدة. توفر let و const تحكماً أفضل في نطاق المتغيرات بفضل نطاق الكتلة، مما يقلل من الأخطاء المحتملة ويزيد من وضوح الكود.
لأفضل الممارسات، يُنصح باستخدام const قدر الإمكان للمتغيرات التي لا تتغير قيمتها (مثل الثوابت أو الكائنات التي لا يتم إعادة تعيينها بالكامل). إذا كانت هناك حاجة لتحديث قيمة المتغير، فاستخدم let. هذا النهج لا يجعل الكود أكثر قابلية للقراءة والصيانة فحسب، بل يضمن أيضاً استخدام الميزات الحديثة للغة JavaScript بفعالية، مما يؤدي إلى تطبيقات أكثر قوة وموثوقية.