تخطَّ إلى المحتوى

دوال المصفوفات المتقدمة في JavaScript: find و sort و reduce

الدرس 17 من 28 · JavaScript

في درس المصفوفات تعلمت push و map و filter. لكن المصفوفات في JavaScript تملك دوالاً أخرى كثيرة تختصر عليك حلقات كاملة: ابحث عن أول منتج سعره أقل من 100، وتأكد أن كل الطلاب ناجحون، ورتّب الأسعار تصاعدياً، واحسب إجمالي السلة. في هذا الدرس ستتعلم أهم هذه الدوال، وستجد أنك تستخدمها في كل مشروع.

البيانات التي سنعمل عليها

Section titled “البيانات التي سنعمل عليها”

سنستخدم في أغلب الأمثلة مصفوفة منتجات:

const products = [
{ name: "قلم", price: 5, inStock: true },
{ name: "حقيبة", price: 250, inStock: false },
{ name: "دفتر", price: 20, inStock: true },
{ name: "آلة حاسبة", price: 120, inStock: true },
];

البحث عن عنصر: find و findIndex

Section titled “البحث عن عنصر: find و findIndex”

filter تُرجع كل العناصر المطابقة في مصفوفة. أما find فتُرجع أول عنصر يطابق الشرط وتتوقف:

const expensive = products.find((p) => p.price > 100);
console.log(expensive.name);
const missing = products.find((p) => p.price > 1000);
console.log(missing);
const index = products.findIndex((p) => p.name === "دفتر");
console.log(index);
الناتج
حقيبة
undefined
2
  • find تُرجع العنصر نفسه، أو undefined إذا لم يوجد.
  • findIndex تُرجع رقم العنصر، أو -1 إذا لم يوجد.

تخيّل find مثل البحث عن كتاب في الرف: تمرّ على الكتب واحداً واحداً، وتتوقف عند أول كتاب يطابق ما تريد.

للقيم البسيطة مثل النصوص والأرقام استخدم includes:

const colors = ["أحمر", "أخضر", "أزرق"];
console.log(colors.includes("أخضر"));
console.log(colors.includes("أصفر"));
الناتج
true
false

وللشروط استخدم some و every:

const anyOut = products.some((p) => !p.inStock);
const allCheap = products.every((p) => p.price < 300);
const allInStock = products.every((p) => p.inStock);
console.log(anyOut);
console.log(allCheap);
console.log(allInStock);
الناتج
true
true
false
  • some: هل يحقق الشرط عنصر واحد على الأقل؟
  • every: هل تحقق الشرط كل العناصر؟

الترتيب في JavaScript فيه فخ مشهور. انظر:

const numbers = [10, 9, 100, 25];
numbers.sort();
console.log(numbers);
الناتج
[ 10, 100, 25, 9 ]

هذا ليس ترتيباً صحيحاً! السبب أن sort بدون معاملات تحوّل العناصر إلى نصوص وترتبها أبجدياً، والنص "100" يأتي قبل "25" لأن 1 قبل 2.

الحل أن تعطيها دالة مقارنة:

numbers.sort((a, b) => a - b);
console.log(numbers);
numbers.sort((a, b) => b - a);
console.log(numbers);
الناتج
[ 9, 10, 25, 100 ]
[ 100, 25, 10, 9 ]

دالة المقارنة تأخذ عنصرين وتُرجع رقماً: إذا كان سالباً يأتي a أولاً، وإذا كان موجباً يأتي b أولاً. فالصيغة a - b للترتيب التصاعدي، و b - a للتنازلي.

ولترتيب كائنات نقارن إحدى خصائصها. وللنصوص العربية استخدم localeCompare:

const byPrice = [...products].sort((a, b) => a.price - b.price);
console.log(byPrice.map((p) => p.name));
const names = ["منى", "أحمد", "سارة"];
names.sort((a, b) => a.localeCompare(b, "ar"));
console.log(names);
الناتج
[ 'قلم', 'دفتر', 'آلة حاسبة', 'حقيبة' ]
[ 'أحمد', 'سارة', 'منى' ]

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

const prices = [5, 250, 20, 120];
const total = prices.reduce((sum, price) => sum + price, 0);
console.log(total);
الناتج
395
  • sum: المُجمِّع، وهو النتيجة المتراكمة حتى الآن.
  • price: العنصر الحالي.
  • 0: القيمة التي يبدأ بها المُجمِّع.

خطوة بخطوة: يبدأ sum من 0، ثم يصبح 5، ثم 255، ثم 275، ثم 395.

ومع الكائنات نجمع خاصية محددة، ويمكن أن ندمج filter معها:

const stockValue = products
.filter((p) => p.inStock)
.reduce((sum, p) => sum + p.price, 0);
console.log(stockValue);
الناتج
145

هذا الأسلوب يسمى سلسلة الدوال (Chaining): ناتج كل دالة يدخل إلى التي بعدها. يُقرأ كجملة: «خذ المنتجات، واختر المتوفر منها، واجمع أسعاره».

الاسمان متشابهان والفرق بينهما كبير:

const letters = ["أ", "ب", "ج", "د", "هـ"];
const firstThree = letters.slice(0, 3);
console.log(firstThree);
console.log(letters.length);
const removed = letters.splice(1, 2);
console.log(removed);
console.log(letters);
الناتج
[ 'أ', 'ب', 'ج' ]
5
[ 'ب', 'ج' ]
[ 'أ', 'د', 'هـ' ]
slice(start, end) splice(start, count)
ماذا تفعل تنسخ جزءاً من المصفوفة تحذف عناصر من المصفوفة نفسها
تغيّر الأصل لا نعم
المعامل الثاني رقم التوقف، وهو غير داخل في النتيجة عدد العناصر المحذوفة

slice(0, 3) نسخت العناصر من الرقم 0 إلى ما قبل الرقم 3، وبقي طول الأصل 5. أما splice(1, 2) فحذفت عنصرين ابتداءً من الرقم 1 من المصفوفة نفسها.

الدالة تُرجع تغيّر الأصل؟
find أول عنصر مطابق أو undefined لا
findIndex رقم أول عنصر مطابق أو -1 لا
includes true أو false لا
some و every true أو false لا
sort المصفوفة نفسها بعد ترتيبها نعم
reduce قيمة واحدة لا
slice نسخة من جزء لا
splice العناصر المحذوفة نعم

عدّل الدرجات، واحسب المتوسط، ورتّب الطلاب:

جرّب بنفسكJavaScript
أو اضغط Ctrl + Enter

الناتج

جرّب أن تغيّر درجة عمر إلى 60 وشاهد كيف تتغير النتائج. ثم أضف طالباً رابعاً.

  • ترتيب الأرقام بـ sort() بدون دالة مقارنة: تحصل على ترتيب أبجدي خاطئ. اكتب دائماً (a, b) => a - b.
  • نسيان القيمة الابتدائية في reduce: إذا كانت المصفوفة فارغة ولم تحدد قيمة ابتدائية يحدث خطأ. اكتب القيمة الابتدائية دائماً.
  • الخلط بين slice و splice: الأولى تنسخ ولا تغيّر، والثانية تحذف من الأصل.

سؤال

ما ناتج [10, 9, 100].sort() بدون دالة مقارنة؟

سؤال

أي دالة تُرجع أول عنصر يحقق الشرط؟

  • find و findIndex للبحث عن أول عنصر مطابق، و includes للقيم البسيطة.
  • some تسأل «هل يوجد واحد؟»، و every تسأل «هل الكل؟».
  • sort تحتاج دالة مقارنة للأرقام، وتغيّر المصفوفة الأصلية.
  • reduce تحوّل المصفوفة إلى قيمة واحدة بمُجمِّع وقيمة ابتدائية.
  • slice تنسخ جزءاً، و splice تحذف من الأصل.