المصفوفات تأتي ومعها صندوق أدوات جاهز
مصفوفات جافاسكربت تحمل معها مجموعة كبيرة من الدوال الجاهزة. معظم ما قد تكتبه داخل حلقة for — سواء لتحويل القيم أو انتقاء بعضها أو جمعها — له دالة تؤدي المهمة في سطر واحد، بقراءة أوضح وقابلية دمج أنيقة مع بقية الدوال.
الطاقم الأساسي يتكوّن من ثلاث دوال: map وfilter وreduce. أتقن هذه الثلاثة مع حفنة من أخواتها، وسترى الكود المليء بالحلقات يتقلّص إلى شيء تفهمه بنظرة سريعة.
كل واحدة من هذه الدوال تستقبل دالة رد نداء (callback) وتُعيد شيئًا ما. ولاحظ أن أيًا منها لم تُعدّل nums — وهذه فكرة جوهرية يجدر بك استيعابها مبكرًا.
دالة map: تحويل كل عنصر في المصفوفة
تستقبل map دالةً وتُنفّذها على كل عنصر، ثم تجمع القيم المُعادة في مصفوفة جديدة بنفس الطول. استخدمها عندما تريد "مُخرجًا واحدًا لكل مُدخل".
الدالة الـ callback كذلك بتستقبل الـ index كبارامتر ثاني لو احتجته: arr.map((item, i) => ...). ولو ما تحتاجه، تجاهله ببساطة.
من الأخطاء الشائعة: استخدام map في حالة أنت أصلاً ما تحتاج المصفوفة الجديدة اللي ترجعها. إذا كان هدفك مجرد طباعة كل عنصر أو حفظه في قاعدة بيانات، فالأنسب هنا forEach أو حلقة تكرار عادية.
filter في javascript: احتفظ بالعناصر المطابقة فقط
دالة filter تستدعي predicate — أي دالة ترجع true أو false — على كل عنصر، وتحتفظ بالعناصر اللي ترجع قيمة صادقة (truthy). المصفوفة الجديدة تكون بنفس الطول أو أقصر منه.
map و filter تتسلسل مع بعضها بشكل طبيعي. اقرأ السلسلة كأنها خط أنابيب (pipeline) خطوة بخطوة:
فلتر أولًا، ثم map — بهذه الطريقة لن يشتغل map إلا على العناصر الناجية من الفلترة.
reduce في javascript: اختزال المصفوفة إلى قيمة واحدة
تُعدّ reduce الأكثر شمولًا بين الثلاث. تُمرّر لها دالة اختزال على الشكل (accumulator, item) => newAccumulator مع قيمة ابتدائية، فتمرّ على عناصر المصفوفة عنصرًا عنصرًا، وتُغذّي الدالة بكل عنصر مع القيمة المتراكمة حتى اللحظة، ثم تُعيد لك القيمة النهائية للمراكم بعد انتهاء المرور.
لا يُشترط أن تكون النتيجة رقمًا؛ يمكن أن تكون كائنًا أو مصفوفة أخرى أو نصًا — أي شيء تبنيه تدريجيًا:
مرّر دائمًا القيمة الابتدائية (الوسيط الثاني). بدونها، يأخذ reduce أول عنصر في المصفوفة ويجعله قيمة المُجمِّع (accumulator) الابتدائية، وهذا يُسبِّب خطأً مع المصفوفات الفارغة، كما أنّه غالبًا لا يُعطيك النتيجة التي تتوقّعها.
reduce دالّة قويّة، لكنّها تصبح صعبة القراءة عندما يتعقّد منطقها. فإذا وجدت أنّ دالة التجميع (الـ reducer) تتجاوز بضعة أسطر، فحلقة for...of البسيطة تكون أوضح في الغالب.
forEach: تنفيذ إجراءات جانبية دون قيمة راجعة
تشبه forEach الدالةَ map لكن بدون إرجاع مصفوفة جديدة. استخدمها حين تريد فقط تنفيذ شيء ما على كلّ عنصر — كطباعته في الـ console، أو استدعاء API، أو تحديث الـ DOM — دون الحاجة إلى مصفوفة ناتجة.
وهنا يظهر الفرق بين map و forEach: الأولى تُنتج مصفوفة جديدة، بينما الثانية تُنفِّذ التأثيرات الجانبية فقط.
شيء مهم لازم تعرفه:
forEachترجعundefined، يعني ما تقدر تسلسل.map()بعدها.- ما تقدر تستخدم
breakللخروج المبكر منforEach. إذا احتجت تخرج مبكراً، استخدمfor...ofأوsome/every.
لو لقيت نفسك تكتب arr.forEach(x => results.push(transform(x)))، فهذا في الحقيقة map.
دالة find و findIndex: عنصر واحد بس يكفي
دالة find تُرجع أول عنصر يحقق الشرط، أو undefined إذا ما في أي عنصر مطابق. أما findIndex فترجع الفهرس (أو -1 إذا ما لقى شيء).
find تتوقف عند أول تطابق. لا تستخدم filter(...)[0] — لأنها تمر على المصفوفة كاملة ثم ترمي الباقي بلا فائدة.
some و every: أسئلة تُجاب بنعم أو لا
ترجع some القيمة true إذا اجتاز عنصر واحد على الأقل الشرط، بينما every لا ترجع true إلا إذا اجتازت جميع العناصر الشرط.
كلتاهما تعتمد على التقييم المبكر (short-circuit) — فـ some تتوقف عند أول true، وevery تتوقف عند أول false. هما الأداة المثالية للإجابة عن أسئلة من نوع "هل يوجد عنصر…؟" أو "هل جميع العناصر…؟".
الفرق بين slice و splice: نسخ أم قص؟
الاسمان متشابهان، لكن وظيفة كل منهما مختلفة تمامًا.
الدالة slice(start, end) تُرجع نسخة سطحية (shallow copy) من جزء من المصفوفة دون أن تُعدِّل الأصل إطلاقًا. المعامل end غير شامل، وإن حذفته ستمتد النسخة حتى نهاية المصفوفة.
splice(start, deleteCount, ...items) تعدّل المصفوفة نفسها مباشرةً؛ إذ تحذف عدد deleteCount من العناصر ابتداءً من الموضع start، ويمكنك أيضًا إدراج عناصر جديدة مكانها، ثم تُرجع الدالة العناصر المحذوفة.
قاعدة للحفظ: slice آمنة (تنسخ المصفوفة)، بينما splice تُعدِّل المصفوفة الأصلية مباشرة بدقّة جراحية.
الدوال التي تعدّل المصفوفة مقابل الدوال التي لا تعدّلها
هذا التمييز مهم جدًا، لأن الشيفرة التي تُعدِّل مصفوفة مشتركة عن غير قصد تُنتج واحدة من أكثر الأخطاء إرهاقًا عند تتبُّعها.
دوال تُعدِّل الأصل (تُغيِّر المصفوفة الأصلية، وعادةً تُرجع شيئًا آخر):
push،pop،shift،unshiftsplice،sort،reversefill،copyWithin
دوال لا تُعدِّل الأصل (تُرجع مصفوفة أو قيمة جديدة دون المساس بالأصل):
map،filter،slice،concatflat،flatMapfind،findIndex،some،every،includes،indexOfreduce،reduceRight
الاثنتان اللتان يجب الانتباه لهما هما sort وreverse — تبدوان بريئتين لكنهما تُعدِّلان المصفوفة بهدوء. إن أردت نسخة مرتَّبة، استخدم slice أولًا:
أضافت إصدارات JavaScript الحديثة نسخًا لا تُعدِّل المصفوفة الأصلية، وهي: toSorted وtoReversed وtoSpliced وwith. هذه الدوال تُعيد مصفوفة جديدة وتترك الأصلية كما هي. وهي مدعومة في جميع بيئات التشغيل الحالية، فاستخدمها متى توفّرت لديك.
flat و flatMap
الدالة flat تُسطّح المصفوفات المتداخلة بمستوى واحد (أو أكثر إذا مرّرت لها مُعامل العمق). أمّا flatMap فهي ببساطة map متبوعة بـ flat بمستوى واحد، وتفيدك لمّا يكون كل عنصر مُتوقَّع أن يُنتج صفر أو أكثر من المخرجات.
flatMap هي الطريقة الأنظف لـ"توسيع" العناصر — مدخل واحد، مخرجات متعددة — دون الحاجة لخطوة flat() إضافية.
نجمع كل شيء معاً
إليك مثالاً واقعياً بسيطاً. لدينا قائمة طلبات، ونريد حساب إجمالي الإيرادات للطلبات المكتملة التي تجاوزت قيمتها 50 دولاراً:
ثلاث دوال، خط معالجة واحد، وبدون متابعة يدوية للحلقات. كل خطوة تُفصح عن وظيفتها بنفسها. يمكنك دمج استدعاءَي filter في واحد، لكن الفصل بينهما يبقى مقروءًا وقد يفيدك أحيانًا أثناء تنقيح الكود.
التالي: Map و Set
المصفوفات ممتازة للتعامل مع التسلسلات المرتبة، لكنها تصبح مرهِقة حين تحتاج إلى بحث سريع بالمفتاح أو إلى تجميعة من القيم الفريدة. ولهذا بالذات وفّرت جافاسكربت هيكلين جاهزين للبيانات هما Map و Set، وسنتناولهما في الصفحة التالية.
الأسئلة الشائعة
ما الفرق بين map و filter و reduce في JavaScript؟
map تُحوّل كل عنصر وتُعيد مصفوفة جديدة بنفس الطول. filter تحتفظ فقط بالعناصر التي تمرّ بالاختبار وتُعيد مصفوفة جديدة (غالباً أقصر). أما reduce فتمرّ على المصفوفة وتطويها إلى قيمة واحدة — مجموع، كائن، مصفوفة أخرى، أو أي شيء تبنيه تدريجياً.
ما الفرق بين forEach و map؟
forEach تُنفّذ دالة على كل عنصر وتُعيد undefined — أي أنها للتأثيرات الجانبية فقط. بينما map تُنفّذ دالة على كل عنصر وتُعيد مصفوفة جديدة بالنتائج. لو كنت تريد مصفوفة محوّلة، استخدم map. ولو كنت تريد فقط تنفيذ شيء على كل عنصر دون الحاجة إلى النتيجة، استخدم forEach (أو حلقة for...of).
ما الدوال التي تُعدّل المصفوفة الأصلية؟
الدوال التي تُعدّل المصفوفة هي: push و pop و shift و unshift و splice و sort و reverse و fill و copyWithin. أما البقية — مثل map و filter و slice و concat و flat و flatMap و find و some و every و reduce — فلا تمسّ المصفوفة الأصلية وتُعيد قيمة جديدة.
متى أستخدم slice ومتى أستخدم splice؟
slice(start, end) تُعيد نسخة سطحية من جزء من المصفوفة دون أن تمسّ الأصلية. أما splice(start, deleteCount, ...items) فتُعدّل المصفوفة نفسها — تحذف و/أو تُدرج عناصر في مكانها وتُعيد العناصر المحذوفة. طريقة سهلة للتذكّر: slice آمنة، و splice تُجري عملية جراحية على المصفوفة.