Menu
العربية
جرّب في Playground

الدوال عالية الرتبة في JavaScript: map و filter و reduce

تعرّف على الدوال عالية الرتبة في جافا سكريبت: كيف تمرّر دالة كوسيط، وكيف ترجع دالة من دالة أخرى، مع شرح عملي لـ map و filter و reduce.

الدوال في جافا سكريبت قيم مثل غيرها

في جافا سكريبت، الدالة قيمة مثلها مثل أي قيمة أخرى. تقدر تخزّنها في متغيّر، أو تحطّها داخل مصفوفة، أو تمرّرها لدالة ثانية، أو حتى ترجعها من داخل دالة. هذه النقطة وحدها تفتح لك أسلوبًا كاملًا في البرمجة:

index.js
Output
Click Run to see the output here.

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

تمرير دالة كوسيط لدالة أخرى

الصورة الأكثر شيوعًا: دالة تستقبل دالة callback وتُشغّلها نيابةً عنك. وقد استخدمت هذا النمط من قبل دون أن تنتبه إليه.

index.js
Output
Click Run to see the output here.

دالة forEach هي دالة عالية الرتبة — تستقبل دالتك وتستدعيها مرة لكل عنصر. ودالة setTimeout أيضاً عالية الرتبة — تأخذ دالتك وتنفّذها بعد فترة زمنية محدّدة. أنت تهتم بـ_ماذا_ تفعل، وهي تتكفّل بـ_متى_ و_كم مرّة_.

وكتابة دالة مشابهة خاصة بك لا تختلف كثيراً. إليك دالة صغيرة تُشغّل الـ callback فقط إذا تحقّق شرط معيّن:

index.js
Output
Click Run to see the output here.

action مجرد باراميتر صادف أنه يحمل دالة. وعندما تستدعيه بالشكل action() فأنت تُشغّل ما تم تمريره إليه.

أهم ثلاث دوال ستستخدمها فعلياً: map و filter و reduce

المصفوفات في جافا سكريبت تأتي مزوّدة بدوال عالية الرتبة تُغنيك عن معظم حلقات for التي قد تكتبها. بمجرد أن تُتقن هذه الثلاث، ستجد أن كمّاً كبيراً من الكود اليومي أصبح أقصر وأوضح.

map — لتحويل كل عنصر

index.js
Output
Click Run to see the output here.

تستدعي map الدالة التي تمررها لها مرة واحدة لكل عنصر، ثم تجمع القيم المُرجَعة في مصفوفة جديدة. نفس الطول، لكن المحتوى بعد التحويل. والمصفوفة الأصلية تبقى كما هي دون تعديل.

filter — احتفظ بالعناصر التي تحقق الشرط

index.js
Output
Click Run to see the output here.

filter يحتفظ بالعناصر التي يُرجع لها الـ callback قيمة صادقة (truthy)، ويتجاهل الباقي. الناتج مصفوفة جديدة قد تكون أقصر من الأصلية.

reduce — اختزال المصفوفة إلى قيمة واحدة

index.js
Output
Click Run to see the output here.

أما reduce فهي الأداة الشاملة متعددة الاستخدامات. تستقبل دالة الـ callback قيمتين: المُجمِّع (accumulator) الحالي والعنصر الذي نعالجه الآن، وكل ما تُرجعه يصير هو المُجمِّع في الدورة التالية. والوسيط الثاني (هنا 0) هو القيمة الابتدائية.

ويمكنك تسلسل هذه الدوال معًا (chaining)، وهنا تظهر قوة هذا الأسلوب فعلًا:

index.js
Output
Click Run to see the output here.

اقرأه من الأعلى إلى الأسفل: احتفظ بالطلبات المدفوعة، واستخرج السعر، ثم اجمعها. بدون حلقات، ولا عدّاد قابل للتغيير، ولا قلق من خطأ في الفهرسة (off-by-one).

إرجاع دالة من داخل دالة

هذا هو النصف الثاني من مفهوم الدوال عالية الرتبة (higher order functions): دالة تُنشئ دالة أخرى وتُعيدها:

index.js
Output
Click Run to see the output here.

عند استدعاء multiplyBy(2) مرة واحدة، تحصل على دالة جديدة تمامًا. هذه الدالة الجديدة لا تزال تتذكر قيمة factor — وهذا ما يُعرف بـ الإغلاق (closure)، وله صفحة مستقلة لاحقًا. المهم الآن أن تستوعب الفكرة: كل مرة تستدعي فيها multiplyBy بوسيط مختلف، تحصل على دالة متخصصة جديدة مبنية من نفس القالب.

هذا النمط ستصادفه في كل مكان:

index.js
Output
Click Run to see the output here.

تعريف واحد، ودالتان قابلتان لإعادة الاستخدام. أفضل بكثير من كتابة warn وinfo يدويًا ومحاولة إبقائهما متزامنتين.

الدوال المُسمّاة مقابل دوال callback المباشرة

تقدر تمرّر دالة سهمية مباشرةً (inline)، أو تمرّر دالة عن طريق اسمها. الطريقتان تشتغلان تمام — اختر ما يقرأ أوضح بالنسبة لك:

index.js
Output
Click Run to see the output here.

عندما تُمرّر isEven (بدون أقواس)، فأنت تُسلّم الدالة نفسها. أما إضافة () فستستدعيها فوراً وتُمرّر النتيجة بدلاً منها، وهذا خطأ شائع بين المبتدئين:

nums.filter(isEven);     // صحيح: يُمرِّر الدالة
nums.filter(isEven());   // خطأ: يستدعي isEven بدون وسائط ويُمرِّر النتيجة

إذا بدأ الـ callback يتمدّد ويتجاوز سطرين أو ثلاثة، اسحبه خارجًا وأعطه اسمًا. الكود المحيط عادةً ما يصبح أوضح للقراءة بهذه الطريقة.

مثال تطبيقي

تتألق الدوال عالية الرتبة حين تُركّب قطعًا صغيرة مع بعضها. لنفترض أن لديك قائمة منتجات، وتريد استخراج أسماء العناصر الرخيصة والمتوفرة في المخزون، مكتوبةً بأحرف كبيرة:

index.js
Output
Click Run to see the output here.

كل دالة مساعدة تقوم بمهمة واحدة، وكل ميثود على المصفوفة يُنفّذ تحويلًا واحدًا. السلسلة (pipeline) في النهاية تُقرأ كأنها وصف لما تريده، لا شرحًا لكيفية الدوران على العناصر.

متى لا تستخدم هذه الدوال؟

الدوال عالية الرتبة في جافا سكريبت رائعة، لكنها ليست بديلًا عن الحلقات في كل موقف:

  • إذا احتجت إلى التوقف في منتصف الطريق، فإن for أو for...of مع break أوضح بكثير من محاولة الخروج من forEach.
  • إذا كنت تنفّذ عمليات غير متزامنة (async) داخل الـ callback، فلن ينتظرها map ولا forEach. استخدم for...of مع await، أو Promise.all مع map.
  • إذا كان الـ callback يُعدّل حالة مشتركة، فأنت تبتعد عن نقاط قوة هذا الأسلوب. استخدم حلقة عادية، أو أعد صياغة الكود بحيث يُعيد قيمًا جديدة.

عند استخدامها في مكانها المناسب، تُغنيك map وfilter وreduce عن معظم الكود التكراري المتعلق بالحلقات في شغلك اليومي. أما استخدامها في كل مكان، فيبدأ يضر بقابلية القراءة. اختر الأداة التي تجعل نيّتك أوضح.

التالي: الكائنات (Objects)

الدوال ليست القيم الوحيدة التي يستحق البناء عليها. الكائنات هي حصان العمل في جافا سكريبت لتجميع البيانات والسلوك المرتبطين معًا، وهي ما كانت تمتلئ به معظم المصفوفات التي فلترتها وحوّلتها للتو. هذا هو موضوع الصفحة التالية.

الأسئلة الشائعة

ما هي الدالة عالية الرتبة في جافا سكريبت؟

الدالة عالية الرتبة (Higher-Order Function) هي دالة تقوم بواحدة على الأقل من اثنتين: إمّا أن تستقبل دالة أخرى كوسيط، أو تُرجع دالة كنتيجة. فمثلًا Array.prototype.map و setTimeout و addEventListener كلها دوال عالية الرتبة لأنها تستقبل دالة callback وتتولّى تنفيذها نيابةً عنك.

ما الفرق بين map و filter و reduce؟

map تحوّل كل عنصر في المصفوفة وتُرجع مصفوفة جديدة بنفس الطول. filter تحتفظ فقط بالعناصر التي تُرجع لها دالة الـ callback قيمة صادقة (truthy)، فتحصل على مصفوفة أقصر أو مساوية. أما reduce فتختزل المصفوفة كلها في قيمة واحدة عبر دمج العناصر عنصرًا تلو الآخر. الثلاثة دوال عالية الرتبة لأنها تأخذ callback.

لماذا نُرجع دالة من داخل دالة أخرى؟

لبناء دوال مساعدة صغيرة وقابلة للتخصيص دون تكرار المنطق نفسه. مثلًا multiplyBy(n) تُرجع دالة جديدة تضرب في n، فيصبح لديك عبر multiplyBy(2) و multiplyBy(10) دالتان متخصّصتان من تعريف واحد. هذا الأسلوب يعتمد على الـ closures، وتراه كثيرًا في معالجات الأحداث والـ middleware ومكتبات الأدوات.

تعلّم البرمجة مع Coddy

ابدأ الآن