لماذا هذا السؤال يحير حتى المطورين ذوي الخبرة؟
\nإذا كنت قد كتبت في JavaScript ولو مرة واحدة، فمن المحتمل أنك صادفت كلا الطريقتين لتعريف الدوال. تبدو متشابهة، لكنها تتصرف بشكل مختلف تمامًا. على Stack Overflow، جمع هذا السؤال آلاف المشاهدات وعشرات الصفحات من النقاشات. لماذا؟ لأن عدم فهم الفرق بين var functionName = function() {} و function functionName() {} يؤدي إلى أخطاء يصعب تتبعها.
اليوم سنحسم هذا الموضوع مرة وإلى الأبد. ستتعرف على ما هو إعلان الدالة و تعبير الدالة، وكيف يعمل الرفع (hoisting)، ومتى تستخدم أي طريقة. هيا بنا!
\n\nما الذي يخفيه التركيب النحوي؟
\nلنبدأ بالأساسيات. الخيار الأول هو إعلان الدالة:
\nfunction greet() {\n console.log('مرحبًا!');\n}\nالخيار الثاني هو تعبير الدالة:
\nconst greet = function() {\n console.log('مرحبًا!');\n};\nلاحظ: في الحالة الأولى، للدالة اسم وتكون في بداية السطر. في الحالة الثانية، نُسند دالة مجهولة إلى المتغير greet. لكن الأمر الأكثر إثارة يبدأ عندما ننظر إلى كيفية معالجة JavaScript لهذه التعريفات.
الفرق الرئيسي: الرفع (Hoisting)
\nالرفع هو آلية في JavaScript، حيث يتم نقل تعريفات المتغيرات والدوال إلى أعلى نطاق الرؤية الخاص بها قبل تنفيذ الكود. لكنه يعمل بشكل مختلف مع الإعلان والتعبير.
\n\nإعلان الدالة يُرفع بالكامل
\nعند استخدام function functionName() {}، تقوم JavaScript برفع تعريف الدالة بالكامل إلى الأعلى. هذا يعني أنه يمكنك استدعاء الدالة قبل تعريفها في الكود:
sayHello(); // "مرحبًا!"\n\nfunction sayHello() {\n console.log('مرحبًا!');\n}\nسيتم تنفيذ الكود دون أخطاء. هذا مريح، لكنه قد يربك المبتدئين.
\n\nتعبير الدالة لا يُرفع
\nمع var functionName = function() {} القصة مختلفة. يتم رفع تعريف المتغير فقط، وليس قيمته. تبقى الدالة في مكانها:
sayHello(); // TypeError: sayHello is not a function\n\nvar sayHello = function() {\n console.log('مرحبًا!');\n};\nلماذا يحدث ذلك؟ لأن var يرفع تعريف المتغير sayHello (بقيمة undefined)، ويتم تعيين الدالة فقط في السطر الذي يحتوي على التعبير. لذلك، قبل هذا السطر، sayHello هو undefined، ومحاولة استدعائه كدالة تؤدي إلى خطأ.
بالمناسبة، إذا استبدلت var بـ let أو const، سيكون الخطأ مختلفًا — ReferenceError: Cannot access 'sayHello' before initialization. هذا مرتبط بأن let و const لا يتم تهيئتهما حتى لحظة التعريف (ما يسمى بـ "المنطقة الميتة المؤقتة").
نطاق الرؤية: حجر عثرة آخر
\nكل من إعلان الدالة و تعبير الدالة يخضعان لقواعد نطاق الرؤية. لكن هناك فارق دقيق.
\n\nإعلان الدالة مرئي داخل الكتلة
\nعلى الرغم من أن إعلان الدالة يُرفع، إلا أن نطاق رؤيته محدود بالكتلة (في الوضع الصارم):
\n'use strict';\nif (true) {\n function greet() {\n console.log('داخل الكتلة');\n }\n greet(); // "داخل الكتلة"\n}\ngreet(); // ReferenceError: greet is not defined (في الوضع الصارم)\nفي الوضع غير الصارم، قد "يتسرب" هذا التعريف إلى الخارج، لكن لا ينبغي الاعتماد على ذلك.
\n\nتعبير الدالة يعيش في متغيره
\nمع تعبير الدالة، كل شيء قابل للتوقع: الدالة متاحة فقط حيث يكون المتغير متاحًا. إذا تم نق