العربية

اكتشف قوة البرمجة الوظيفية باستخدام مصفوفات JavaScript. تعلم كيفية تحويل بياناتك وتصفيتها وتقليصها بكفاءة باستخدام الدوال المضمنة.

إتقان البرمجة الوظيفية باستخدام مصفوفات JavaScript

في المشهد المتطور باستمرار لتطوير الويب، لا تزال JavaScript حجر الزاوية. في حين أن نماذج البرمجة الشيئية والأمرية كانت مهيمنة لفترة طويلة، فإن البرمجة الوظيفية (FP) تكتسب زخمًا كبيرًا. تؤكد البرمجة الوظيفية على عدم القابلية للتغيير، والدوال النقية، والكود التصريحي، مما يؤدي إلى تطبيقات أكثر قوة وقابلية للصيانة والتنبؤ. إحدى أقوى الطرق لتبني البرمجة الوظيفية في JavaScript هي الاستفادة من دوال المصفوفات الأصلية.

سيتعمق هذا الدليل الشامل في كيفية تسخير قوة مبادئ البرمجة الوظيفية باستخدام مصفوفات JavaScript. سنستكشف المفاهيم الأساسية ونوضح كيفية تطبيقها باستخدام دوال مثل map و filter و reduce، مما يغير طريقة معالجتك للبيانات.

ما هي البرمجة الوظيفية؟

قبل الخوض في مصفوفات JavaScript، دعنا نعرّف بإيجاز البرمجة الوظيفية. في جوهرها، البرمجة الوظيفية هي نموذج برمجة يتعامل مع الحساب على أنه تقييم للدوال الرياضية ويتجنب تغيير الحالة والبيانات القابلة للتغيير. تشمل المبادئ الأساسية ما يلي:

يمكن أن يؤدي تبني هذه المبادئ إلى كود أسهل في الفهم والاختبار وتصحيح الأخطاء، خاصة في التطبيقات المعقدة. تتناسب دوال مصفوفات JavaScript تمامًا مع تطبيق هذه المفاهيم.

قوة دوال مصفوفات JavaScript

تأتي مصفوفات JavaScript مزودة بمجموعة غنية من الدوال المضمنة التي تسمح بمعالجة البيانات المعقدة دون اللجوء إلى الحلقات التقليدية (مثل for أو while). غالبًا ما تعيد هذه الدوال مصفوفات جديدة، مما يعزز عدم القابلية للتغيير، وتقبل دوال رد الاتصال (callback functions)، مما يتيح اتباع نهج وظيفي.

دعنا نستكشف دوال المصفوفات الوظيفية الأكثر أساسية:

1. Array.prototype.map()

تُنشئ الدالة map() مصفوفة جديدة مملوءة بنتائج استدعاء دالة معينة على كل عنصر في المصفوفة الأصلية. إنها مثالية لتحويل كل عنصر من عناصر المصفوفة إلى شيء جديد.

الصيغة:

array.map(callback(currentValue[, index[, array]])[, thisArg])

الخصائص الرئيسية:

مثال: مضاعفة كل رقم

تخيل أن لديك مصفوفة من الأرقام وتريد إنشاء مصفوفة جديدة حيث يتم مضاعفة كل رقم.

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

// Using map for transformation
const doubledNumbers = numbers.map(number => number * 2);

console.log(numbers); // Output: [1, 2, 3, 4, 5] (original array is unchanged)
console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

مثال: استخراج الخصائص من الكائنات

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

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const userNames = users.map(user => user.name);

console.log(userNames); // Output: ['Alice', 'Bob', 'Charlie']

2. Array.prototype.filter()

تُنشئ الدالة filter() مصفوفة جديدة تحتوي على جميع العناصر التي تجتاز الاختبار الذي تنفذه الدالة المحددة. تُستخدم لتحديد العناصر بناءً على شرط معين.

الصيغة:

array.filter(callback(element[, index[, array]])[, thisArg])

الخصائص الرئيسية:

مثال: تصفية الأرقام الزوجية

دعنا نصفي مصفوفة الأرقام للاحتفاظ بالأرقام الزوجية فقط.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Using filter to select even numbers
const evenNumbers = numbers.filter(number => number % 2 === 0);

console.log(numbers); // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(evenNumbers); // Output: [2, 4, 6, 8, 10]

مثال: تصفية المستخدمين النشطين

من مصفوفة المستخدمين لدينا، دعنا نصفي المستخدمين الذين تم وضع علامة عليهم كنشطين.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const activeUsers = users.filter(user => user.isActive);

console.log(activeUsers); 
/* Output:
[
  { id: 1, name: 'Alice', isActive: true },
  { id: 3, name: 'Charlie', isActive: true }
]
*/

3. Array.prototype.reduce()

تُنفذ الدالة reduce() دالة رد نداء "reducer" مقدمة من المستخدم على كل عنصر من عناصر المصفوفة، بالترتيب، وتمرر القيمة المُعادة من الحساب على العنصر السابق. النتيجة النهائية لتشغيل الـ reducer عبر جميع عناصر المصفوفة هي قيمة واحدة.

يمكن القول إن هذه الدالة هي الأكثر تنوعًا بين دوال المصفوفات وهي حجر الزاوية للعديد من أنماط البرمجة الوظيفية، مما يتيح لك "تقليص" مصفوفة إلى قيمة واحدة (مثل المجموع، الناتج، العدد، أو حتى كائن أو مصفوفة جديدة).

الصيغة:

array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

الخصائص الرئيسية:

مثال: جمع الأرقام

دعنا نجمع كل الأرقام في مصفوفة لدينا.

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

// Using reduce to sum numbers
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // 0 is the initialValue

console.log(sum); // Output: 15

شرح:

مثال: تجميع الكائنات حسب خاصية

يمكننا استخدام reduce لتحويل مصفوفة من الكائنات إلى كائن حيث يتم تجميع القيم حسب خاصية معينة. دعنا نجمع مستخدمينا حسب حالة `isActive` الخاصة بهم.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: false }
];

const groupedUsers = users.reduce((acc, user) => {
  const status = user.isActive ? 'active' : 'inactive';
  if (!acc[status]) {
    acc[status] = [];
  }
  acc[status].push(user);
  return acc;
}, {}); // Empty object {} is the initialValue

console.log(groupedUsers);
/* Output:
{
  active: [
    { id: 1, name: 'Alice', isActive: true },
    { id: 3, name: 'Charlie', isActive: true }
  ],
  inactive: [
    { id: 2, name: 'Bob', isActive: false },
    { id: 4, name: 'David', isActive: false }
  ]
}
*/

مثال: عد التكرارات

دعنا نعد تكرار كل فاكهة في قائمة.

const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const fruitCounts = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});

console.log(fruitCounts); // Output: { apple: 3, banana: 2, orange: 1 }

4. Array.prototype.forEach()

بينما لا تُرجع forEach() مصفوفة جديدة وغالبًا ما تعتبر أكثر أمرية لأن هدفها الأساسي هو تنفيذ دالة لكل عنصر في المصفوفة، إلا أنها لا تزال طريقة أساسية تلعب دورًا في الأنماط الوظيفية، لا سيما عندما تكون الآثار الجانبية ضرورية أو عند التكرار دون الحاجة إلى إخراج محول.

الصيغة:

array.forEach(callback(element[, index[, array]])[, thisArg])

الخصائص الرئيسية:

مثال: تسجيل كل عنصر

const messages = ['Hello', 'Functional', 'World'];

messages.forEach(message => console.log(message));
// Output:
// Hello
// Functional
// World

ملاحظة: للتحويلات والتصفية، يفضل استخدام map و filter نظرًا لعدم قابليتهما للتغيير وطبيعتهما التصريحية. استخدم forEach عندما تحتاج تحديدًا إلى تنفيذ إجراء لكل عنصر دون جمع النتائج في بنية جديدة.

5. Array.prototype.find() و Array.prototype.findIndex()

هذه الدوال مفيدة لتحديد موقع عناصر معينة في المصفوفة.

مثال: البحث عن مستخدم

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

const bob = users.find(user => user.name === 'Bob');
const bobIndex = users.findIndex(user => user.name === 'Bob');
const nonExistentUser = users.find(user => user.name === 'David');
const nonExistentIndex = users.findIndex(user => user.name === 'David');

console.log(bob); // Output: { id: 2, name: 'Bob' }
console.log(bobIndex); // Output: 1
console.log(nonExistentUser); // Output: undefined
console.log(nonExistentIndex); // Output: -1

6. Array.prototype.some() و Array.prototype.every()

تختبر هذه الدوال ما إذا كانت جميع العناصر في المصفوفة تجتاز الاختبار الذي تنفذه الدالة المحددة.

مثال: التحقق من حالة المستخدم

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true }
];

const hasInactiveUser = users.some(user => !user.isActive);
const allAreActive = users.every(user => user.isActive);

console.log(hasInactiveUser); // Output: true (because Bob is inactive)
console.log(allAreActive); // Output: false (because Bob is inactive)

const allUsersActive = users.filter(user => user.isActive).length === users.length;
console.log(allUsersActive); // Output: false

// Alternative using every directly
const allUsersActiveDirect = users.every(user => user.isActive);
console.log(allUsersActiveDirect); // Output: false

ربط دوال المصفوفات للعمليات المعقدة

تتجلّى القوة الحقيقية للبرمجة الوظيفية مع مصفوفات JavaScript عندما تربط هذه الدوال معًا. نظرًا لأن معظم هذه الدوال تُرجع مصفوفات جديدة (باستثناء forEach)، يمكنك تمرير إخراج دالة بسلاسة إلى إدخال دالة أخرى، مما يؤدي إلى إنشاء مسارات بيانات أنيقة وقابلة للقراءة.

مثال: العثور على أسماء المستخدمين النشطين ومضاعفة معرفاتهم

دعنا نجد جميع المستخدمين النشطين، ونستخرج أسماءهم، ثم ننشئ مصفوفة جديدة حيث يتم إضافة رقم إلى كل اسم يمثل فهرسه في القائمة *المصفاة*، ويتم مضاعفة معرفاتهم.

const users = [
  { id: 1, name: 'Alice', isActive: true },
  { id: 2, name: 'Bob', isActive: false },
  { id: 3, name: 'Charlie', isActive: true },
  { id: 4, name: 'David', isActive: true },
  { id: 5, name: 'Eve', isActive: false }
];

const processedActiveUsers = users
  .filter(user => user.isActive) // Get only active users
  .map((user, index) => ({      // Transform each active user
    name: `${index + 1}. ${user.name}`,
    doubledId: user.id * 2
  }));

console.log(processedActiveUsers);
/* Output:
[
  { name: '1. Alice', doubledId: 2 },
  { name: '2. Charlie', doubledId: 6 },
  { name: '3. David', doubledId: 8 }
]
*/

هذا النهج المتسلسل تصريحي: نحن نحدد الخطوات (تصفية، ثم تحويل) دون إدارة صريحة للحلقات. وهو أيضًا غير قابل للتغيير، حيث تنتج كل خطوة مصفوفة أو كائنًا جديدًا، مما يترك مصفوفة users الأصلية دون مساس.

عدم القابلية للتغيير في الممارسة

تعتمد البرمجة الوظيفية بشكل كبير على عدم القابلية للتغيير. وهذا يعني أنه بدلاً من تعديل هياكل البيانات الموجودة، تقوم بإنشاء هياكل جديدة بالتغييرات المطلوبة. تدعم دوال مصفوفات JavaScript مثل map و filter و slice هذا بشكل طبيعي من خلال إرجاع مصفوفات جديدة.

لماذا تُعد عدم القابلية للتغيير مهمة؟

عندما تحتاج إلى إجراء عملية من شأنها أن تعدل مصفوفة بشكل تقليدي (مثل إضافة أو إزالة عنصر)، يمكنك تحقيق عدم القابلية للتغيير باستخدام دوال مثل slice، أو صيغة الانتشار (...)، أو بدمج دوال وظيفية أخرى.

مثال: إضافة عنصر بشكل غير قابل للتغيير

const originalArray = [1, 2, 3];

// Imperative way (mutates originalArray)
// originalArray.push(4);

// Functional way using spread syntax
const newArrayWithPush = [...originalArray, 4];
console.log(originalArray); // Output: [1, 2, 3]
console.log(newArrayWithPush); // Output: [1, 2, 3, 4]

// Functional way using slice and concatenation (less common now)
const newArrayWithSlice = originalArray.slice(0, originalArray.length).concat(4);
console.log(newArrayWithSlice); // Output: [1, 2, 3, 4]

مثال: إزالة عنصر بشكل غير قابل للتغيير

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

// Remove element at index 2 (value 3)

// Functional way using slice and spread syntax
const newArrayAfterSplice = [
  ...originalArray.slice(0, 2),
  ...originalArray.slice(3)
];
console.log(originalArray); // Output: [1, 2, 3, 4, 5]
console.log(newArrayAfterSplice); // Output: [1, 2, 4, 5]

// Using filter to remove a specific value
const newValueToRemove = 3;
const arrayWithoutValue = originalArray.filter(item => item !== newValueToRemove);
console.log(arrayWithoutValue); // Output: [1, 2, 4, 5]

أفضل الممارسات والتقنيات المتقدمة

عندما تصبح أكثر ارتياحًا لدوال المصفوفات الوظيفية، ضع في اعتبارك هذه الممارسات:

مثال: نهج وظيفي لتجميع البيانات

تخيل أن لديك بيانات مبيعات من مناطق مختلفة وتريد حساب إجمالي المبيعات لكل منطقة، ثم العثور على المنطقة ذات أعلى مبيعات.

const salesData = [
  { region: 'North', amount: 100 },
  { region: 'South', amount: 150 },
  { region: 'North', amount: 120 },
  { region: 'East', amount: 200 },
  { region: 'South', amount: 180 },
  { region: 'North', amount: 90 }
];

// 1. Calculate total sales per region using reduce
const salesByRegion = salesData.reduce((acc, sale) => {
  acc[sale.region] = (acc[sale.region] || 0) + sale.amount;
  return acc;
}, {});

// salesByRegion will be: { North: 310, South: 330, East: 200 }

// 2. Convert the aggregated object into an array of objects for further processing
const salesArray = Object.keys(salesByRegion).map(region => ({
  region: region,
  totalAmount: salesByRegion[region]
}));

// salesArray will be: [
//   { region: 'North', totalAmount: 310 },
//   { region: 'South', totalAmount: 330 },
//   { region: 'East', totalAmount: 200 }
// ]

// 3. Find the region with the highest sales using reduce
const highestSalesRegion = salesArray.reduce((max, current) => {
  return current.totalAmount > max.totalAmount ? current : max;
}, { region: '', totalAmount: -Infinity }); // Initialize with a very small number

console.log('Sales by Region:', salesByRegion);
console.log('Sales Array:', salesArray);
console.log('Region with Highest Sales:', highestSalesRegion);

/*
Output:
Sales by Region: { North: 310, South: 330, East: 200 }
Sales Array: [
  { region: 'North', totalAmount: 310 },
  { region: 'South', totalAmount: 330 },
  { region: 'East', totalAmount: 200 }
]
Region with Highest Sales: { region: 'South', totalAmount: 330 }
*/

الخاتمة

البرمجة الوظيفية باستخدام مصفوفات JavaScript ليست مجرد خيار أسلوبي؛ إنها طريقة قوية لكتابة كود أنظف وأكثر قابلية للتنبؤ وأكثر قوة. من خلال تبني دوال مثل map و filter و reduce، يمكنك تحويل بياناتك واستعلامها وتجميعها بفعالية مع الالتزام بالمبادئ الأساسية للبرمجة الوظيفية، لا سيما عدم القابلية للتغيير والدوال النقية.

مع استمرارك في رحلتك في تطوير JavaScript، سيؤدي دمج هذه الأنماط الوظيفية في سير عملك اليومي بلا شك إلى تطبيقات أكثر قابلية للصيانة والتوسع. ابدأ بتجربة دوال المصفوفات هذه في مشاريعك، وستكتشف قريبًا قيمتها الهائلة.