طريقة أقصر لكتابة الدوال في جافا سكريبت
الدوال السهمية في جافا سكريبت (arrow functions) هي صياغة مختصرة لتعريف الدالة. ظهرت مع إصدار ES2015، وأصبحت اليوم الأسلوب الافتراضي عند كتابة دوال صغيرة ومختصرة، خصوصًا الـ callbacks التي نمررها لدوال المصفوفات، ومعالجات الـ promises، والمستمعين للأحداث.
الصيغة بسيطة: المعاملات، ثم =>، ثم جسم الدالة.
نفس النتيجة التي نحصل عليها بالصيغة التقليدية:
const add = function (a, b) {
return a + b;
};
اختصار الكتابة شيء جميل، لكن السبب الحقيقي لأهمية الدوال السهمية في جافا سكريبت هو ميزتان أساسيتان: الإرجاع الضمني وطريقة تعاملها مع this. سنتناول كلتيهما بالتفصيل.
الإرجاع الضمني في جافا سكريبت
إذا كان جسم الدالة عبارة عن تعبير واحد فقط، يمكنك الاستغناء عن الأقواس المعقوفة وعن كلمة return، وستُرجع الدالة قيمة هذا التعبير تلقائيًا:
هنا تتجلى قوة الدوال السهمية في جافا سكريبت — فالتحويلات المكتوبة في سطر واحد تُقرأ وكأنها معادلات رياضية:
لحظة ما تحتاج أكثر من سطر واحد، بترجع للأقواس المعقوفة ولازم تكتب return بشكل صريح:
من أكثر الأخطاء شيوعًا في الدوال السهمية نسيانُ كلمة return داخل الأقواس المعقوفة. فمثلًا x => { x * 2 } يُرجع undefined، لأن الأقواس المعقوفة تُحوّل الجسم إلى كتلة تعليمات، وبالتالي يُهمَل التعبير الموجود بداخلها.
الأقواس حول المعاملات
إذا كانت الدالة تأخذ معاملًا واحدًا فقط، فالأقواس حوله اختيارية:
مع صفر أو اثنين أو أكثر من المعاملات، لا بد من الأقواس:
بعض الفرق تفضّل وضع الأقواس دائمًا بهدف التناسق. كلا الأسلوبين صحيح، فقط اختر واحدًا والتزم به.
إرجاع كائن (Object Literal) من دالة سهمية
هنا تكمن مشكلة شائعة. لو حاولت إرجاع كائن بشكل ضمني، فإن الأقواس المعقوفة ستُفهم على أنها جسم الدالة:
الناتج هنا هو undefined، لأن جافا سكريبت تُفسّر { name: name } على أنها كتلة برمجية تحتوي على عبارة موسومة (labeled statement)، لا كائن حرفي. الحل هو تغليف الكائن بين قوسين لإجبار المحرّك على التعامل معه كتعبير:
الأقواس () المحيطة بـ { ... } هي الحل. احفظ هذه النقطة جيدًا، لأنك ستصطدم بها خلال أول أسبوع من كتابتك للدوال السهمية.
القيمة المعجمية لـ this (lexical this)
السبب الحقيقي لوجود الدوال السهمية في جافا سكريبت ليس اختصار الصياغة، بل أنها لا تملك this خاصًا بها؛ فهي ترث قيمتها من النطاق المحيط بها.
ولكي تتضح أهمية هذه النقطة، لنأخذ مثالًا على دالة عادية تُستخدم كدالة رد نداء (callback):
داخل دالة function () { ... } الممرَّرة إلى setInterval، لا تشير this إلى counter. فالدوال العادية تحصل على this خاصة بها بحسب طريقة استدعائها، وsetInterval يستدعي الـ callback وقيمة this فيها إما undefined (في الوضع الصارم) أو الكائن العام.
أما الدالة السهمية فتحتفظ بقيمة this الخاصة بالتابع المُحيط بها (lexical this):
داخل الدالة السهمية، تبقى قيمة this مشيرةً إلى counter، لأن الدالة السهمية لا تُنشئ this خاصاً بها. قبل ظهور الدوال السهمية في جافا سكريبت، كان المطورون يلجؤون إلى حيل مثل const self = this; أو استخدام .bind(this) للتحايل على هذه المشكلة. هذه الأنماط لا تزال تعمل، لكن نادراً ما نحتاج إليها اليوم.
ما الذي تفتقده الدوال السهمية؟
قاعدة الـ lexical this جزء من نمط أشمل: الدوال السهمية تتخلى عن عدة أشياء تمتلكها الدوال العادية.
- لا تملك
thisخاصاً بها — ترثه من النطاق المحيط بها. - لا يوجد كائن
arguments— استخدم بدلاً منه معاملات الباقي (...args). - لا تعمل مع
new— لا يمكن استعمالها كدالة بانية (constructor). - لا تملك خاصية
prototype— وهذا نتيجة طبيعية لعدم دعمها لـnew.
...args يمنحك نفس قدرة "استقبال أي عدد من الوسائط" التي كان يوفرها arguments، مع ميزة إضافية وهي أنه مصفوفة حقيقية. أما استدعاء new Greeter(...) فيرمي خطأً لأن الدوال السهمية ليست دوال بانية (constructors).
متى لا نستخدم الدوال السهمية في جافا سكريبت
الدوال السهمية هي الخيار الافتراضي لدوال الاستدعاء (callbacks)، لكنها ليست الخيار المناسب في بعض الحالات:
الدوال التي تُعرَّف كـ methods داخل كائن باستخدام الصياغة السهمية لا يرتبط فيها this بالكائن نفسه، بل يرث قيمته من المكان الذي كُتب فيه الكائن (عادةً نطاق الموديول أو النطاق العام). لذلك استخدم الصياغة المختصرة للـ methods مثل greet() { ... } عند تعريف دوال الكائن.
ونفس الكلام ينطبق على methods الـ prototype في الكلاسات، وعلى معالِجات الأحداث التي تحتاج أن يشير this فيها إلى العنصر، وعلى أي دالة تنوي استدعاءها بـ new. في هذه الحالات تكون الدالة العادية function هي الأداة الصحيحة.
قاعدة سريعة تساعدك على الاختيار
- callback قصير بتعبير واحد؟ استخدم arrow function.
- تحتاج أن يبقى
thisمرتبطًا بالنطاق الخارجي؟ استخدم arrow function. - تعرّف method داخل كائن أو على prototype كلاس؟ استخدم دالة عادية (أو الصياغة المختصرة للـ method).
- تكتب constructor؟ دالة عادية، والأفضل من ذلك استخدام
class.
ستصادف الأسلوبين معًا في أي مشروع جافا سكريبت حقيقي. ومعرفة أيّهما يناسب كل موقف تحتاج بضعة أسابيع من قراءة أكواد الآخرين، وبعدها يصبح الاختيار تلقائيًا.
التالي: المعاملات والقيم الافتراضية
تتشارك الدوال السهمية والدوال العادية في نفس خصائص المعاملات: القيم الافتراضية، ومعاملات rest، وتفكيك الوسائط (destructuring)، وغيرها. هذا موضوع الصفحة التالية، وهو ينطبق على كل شكل من أشكال الدوال التي رأيناها حتى الآن.
الأسئلة الشائعة
ما هي الدالة السهمية Arrow Function في جافا سكريبت؟
الدالة السهمية هي طريقة مختصرة لكتابة الدوال باستخدام الرمز =>. مثلاً const add = (a, b) => a + b; دالة سهمية تأخذ وسيطين وتُرجع مجموعهما. إضافة إلى اختصار الصياغة، الدوال السهمية لا تملك this أو arguments أو super خاصة بها، بل ترثها من النطاق المحيط بها.
ما الفرق بين الدالة السهمية والدالة العادية؟
الدالة العادية المُعرَّفة بكلمة function تملك this خاصاً بها، وكائن arguments خاصاً، ويمكن استخدامها كمُنشئ مع new. أما الدالة السهمية فلا شيء من هذا ينطبق عليها، وترث this من المكان الذي عُرِّفت فيه. كذلك لا تُرفع الدوال السهمية (hoisting) كما تُرفع تصريحات function.
متى أستخدم الدالة السهمية؟
استخدمها في الـ callbacks القصيرة مثل arr.map(x => x * 2)، وفي أي مكان تريد أن يبقى فيه this مرتبطاً بالنطاق المحيط — مثل داخل دالة في كلاس تُمرّر معالجاً إلى setTimeout أو إلى event listener. أما للدوال داخل الكائنات (methods) والمُنشئات والدوال المستقلة في أعلى الملف، فالأفضل استخدام function العادية.