مختصر كتابي: Reduce

كيف تحوّل reduce المصفوفة إلى قيمة واحدة؟

الـ reduce مختلفة قليلًا عن methods التكرار التي رأيناها: فهي تمر على كل عناصر المصفوفة وتجمعها بشكل ما، ثم ترجع في النهاية نتيجة واحدة فقط، مثل مجموع الأرقام أو أطول كلمة. وسر عملها متغير اسمه المُجمِّع (Accumulator) يحتفظ بالنتيجة من دورة إلى التي بعدها.

إنفوجرافيك: طريقة عمل reduce والمجمع والقيمة الابتدائية
ملخص الدرس في صورة واحدة. اضغط على الصورة لتكبيرها.

الفرق بين reduce وأخواتها

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

الـ methodماذا ترجع؟
mapمصفوفة جديدة بنفس الطول بعد تحويل العناصر
findعنصرًا واحدًا فقط يحقق الشرط
filterكل العناصر التي تحقق الشرط
forEachلا ترجع شيئًا، تمر على العناصر فقط
reduceقيمة واحدة نهائية

جمع كل الأرقام

دالة الـ callback تستقبل القيمة السابقة (previous value) والقيمة الحالية (current value)، ويمكنها أيضًا أن تستقبل الـ index والمصفوفة الأصلية، لكننا نكتفي بأول اثنين. وما ترجعه الدالة في كل دورة يُحفظ في الـ accumulator ليُستخدم في الدورة التالية. لذلك لو لم نكتب return ستكون قيمته undefined:

const numbers = [1, 2, 3, 4, 5];

numbers.reduce((accumulator, currentValue) => {
  console.log(accumulator, currentValue);
});
// 1 2
// undefined 3
// undefined 4
// undefined 5

والآن نرجع المجموع، ونعطي الـ accumulator قيمة ابتدائية (Initial value) هي 0 بعد الدالة:

const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  0
);

console.log(sum); // 15
الدورةaccumulatorcurrentValueالقيمة المرجعة
1011
2123
3336
46410
510515
  • آخر قيمة مرجعة هي النتيجة النهائية التي تخرج من reduce.
  • لو استخدمت الطرح accumulator - currentValue مع البداية 0 تكون النتيجة -15.
  • القيمة الابتدائية تغيّر النتيجة: مع الجمع والبداية 5 تكون النتيجة 20، ومع البداية 10 تكون 25.

مثال بالكلمات: أطول كلمة

هنا القيمة الابتدائية string فارغ ""، وفي كل دورة نقارن طول الكلمة المحفوظة في الـ accumulator بطول الكلمة الحالية، ونرجع الأطول باستخدام الـ ternary:

const fruits = ["apple", "banana", "avocado", "orange"];

const longestWord = fruits.reduce(
  (accumulator, currentValue) =>
    accumulator.length > currentValue.length ? accumulator : currentValue,
  ""
);

console.log(longestWord); // avocado

المقارنات بالترتيب: "" مع apple فتفوز apple، ثم apple مع banana فتفوز banana، ثم banana مع avocado فتفوز avocado، ثم avocado مع orange فتبقى avocado. ويمكنك كتابة نفس الشرط بـ if وelse لو وجدت الـ ternary طويلة، والطريقتان صحيحتان.

نفس المثال بـ forEach، ولماذا reduce أفضل؟

const numbers = [1, 2, 3, 4, 5];
let count = 0;

numbers.forEach((number) => {
  count += number;
});

console.log(count); // 15

النتيجة نفسها والكود يبدو أقصر، لكن الدالة هنا ليست Pure function، لأنها تغيّر المتغير count الموجود خارجها. أما في reduce فالقيمة الابتدائية والمعاملات كلها داخلها، فتستطيع نقلها إلى أي صفحة أو مشروع آخر وتعمل كما هي. الحلّان صحيحان، لكن reduce هي الـ best practice هنا.

الخلاصة

  • reduce تمر على كل العناصر وترجع قيمة واحدة نهائية.
  • ما ترجعه الـ callback يُحفظ في الـ accumulator للدورة التالية، فلا تنسَ return.
  • القيمة الابتدائية تُكتب بعد الدالة، مثل 0 للأرقام أو "" للكلمات.
  • تفيد في حالات محددة: الجمع والطرح وإيجاد أكبر أو أصغر قيمة.

مصطلحات الدرس

المصطلحبالإنجليزيةالمعنى
الاختزالreduceتحويل المصفوفة كلها إلى قيمة واحدة
المجمِّعAccumulatorيحتفظ بما ترجعه الدالة من دورة إلى أخرى
القيمة الحاليةCurrent valueالعنصر الذي تمر عليه الحلقة الآن
القيمة الابتدائيةInitial valueالقيمة التي يبدأ بها الـ accumulator
الدالة النقيةPure functionدالة لا تعتمد على شيء خارجها ولا تغيّره

هذا درس مجاني من «Javascript Intermediate». اشترك للوصول إلى كل الدروس والاختبارات والشهادة.

اعرف تفاصيل الاشتراك ←