عوامل النشر والتجميع
مقدمة في صيغة النقاط الثلاث
قدم JavaScript ES6 صيغة قوية تمثلها ثلاث نقاط (...) تخدم غرضين مختلفين حسب طريقة الاستخدام. عندما تستخدم لتوسيع او فك العناصر تسمى عامل النشر (Spread). وعندما تستخدم لتجميع او جمع العناصر تسمى معامل التجميع (Rest). على الرغم من مشاركتهما نفس الصيغة، فان هاتين الميزتين تحلان مشاكل مختلفة تماما. فهم عاملي النشر والتجميع ضروري لكتابة JavaScript حديث. فهما يبسطان التعامل مع المصفوفات وتكوين الكائنات وتوقيعات الدوال وانماط التفكيك. بنهاية هذا الدرس ستعرف بالضبط متى وكيف تستخدم كل واحد منهما بفعالية في الكود الخاص بك.
عامل النشر مع المصفوفات
عامل النشر يوسع العنصر القابل للتكرار (مثل المصفوفة) الى عناصر فردية. فكر فيه على انه "فك تغليف" محتويات المصفوفة بحيث يقف كل عنصر بمفرده. هذا مفيد للغاية لنسخ المصفوفات ودمجها وتمرير عناصرها كمعاملات للدوال.
نسخ المصفوفات
قبل عامل النشر كان المطورون يستخدمون طرقا مثل Array.prototype.slice() او Array.from() لانشاء نسخ سطحية من المصفوفات. يوفر عامل النشر طريقة اوضح واسهل قراءة لتحقيق نفس النتيجة. عندما تنشر مصفوفة داخل قوسي مصفوفة جديدة تنشئ مصفوفة جديدة تماما بنفس العناصر. التغييرات على المصفوفة الاصلية لن تؤثر على النسخة والتغييرات على النسخة لن تؤثر على الاصلية.
مثال: نسخ مصفوفة باستخدام النشر
const originalFruits = ['apple', 'banana', 'cherry'];
const copiedFruits = [...originalFruits];
console.log(copiedFruits);
// Output: ['apple', 'banana', 'cherry']
// تعديل النسخة لا يؤثر على الاصلية
copiedFruits.push('date');
console.log(originalFruits); // ['apple', 'banana', 'cherry']
console.log(copiedFruits); // ['apple', 'banana', 'cherry', 'date']
// مقارنة مع الطريقة القديمة
const oldCopy = originalFruits.slice();
console.log(oldCopy); // ['apple', 'banana', 'cherry']
structuredClone() او مكتبة مثل Lodash.مثال: قيود النسخ السطحي
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 }
];
const usersCopy = [...users];
// المصفوفة الخارجية مرجع جديد
console.log(users === usersCopy); // false
// لكن الكائنات الداخلية لا تزال مراجع مشتركة
console.log(users[0] === usersCopy[0]); // true
usersCopy[0].age = 31;
console.log(users[0].age); // 31 -- الاصلية تغيرت ايضا!
// للنسخ العميق الحقيقي استخدم structuredClone
const deepCopy = structuredClone(users);
deepCopy[0].age = 99;
console.log(users[0].age); // 31 -- الاصلية لم تتأثر
دمج المصفوفات
عامل النشر يجعل دمج مصفوفتين او اكثر بديهيا وسهل القراءة. تقوم ببساطة بنشر كل مصفوفة داخل قوسي مصفوفة جديدة. هذا يحل محل طريقة Array.prototype.concat() القديمة بصيغة اكثر وضوحا ومرونة. يمكنك ايضا ادراج عناصر فردية بين المصفوفات المنشورة مما يمنحك تحكما كاملا في الترتيب الناتج.
مثال: دمج المصفوفات
const frontend = ['HTML', 'CSS', 'JavaScript'];
const backend = ['Node.js', 'Python', 'Go'];
const databases = ['PostgreSQL', 'MongoDB'];
// دمج مصفوفتين
const fullstack = [...frontend, ...backend];
console.log(fullstack);
// ['HTML', 'CSS', 'JavaScript', 'Node.js', 'Python', 'Go']
// الدمج مع عناصر اضافية بينها
const allSkills = [...frontend, 'React', 'Vue', ...backend, ...databases];
console.log(allSkills);
// ['HTML', 'CSS', 'JavaScript', 'React', 'Vue', 'Node.js', 'Python', 'Go', 'PostgreSQL', 'MongoDB']
// مقارنة مع طريقة concat القديمة
const oldMerge = frontend.concat(backend).concat(databases);
console.log(oldMerge);
// ['HTML', 'CSS', 'JavaScript', 'Node.js', 'Python', 'Go', 'PostgreSQL', 'MongoDB']
تمرير عناصر المصفوفة الى الدوال
قبل عامل النشر اذا كان لديك مصفوفة من القيم واردت تمريرها كمعاملات فردية لدالة كان عليك استخدام Function.prototype.apply(). عامل النشر يلغي هذه الحاجة تماما. يمكنك نشر مصفوفة مباشرة داخل استدعاء دالة وسيصبح كل عنصر معاملا منفصلا.
مثال: النشر في معاملات الدوال
const numbers = [5, 12, 3, 8, 1, 19, 7];
// ايجاد القيمة القصوى
const max = Math.max(...numbers);
console.log(max); // 19
// ايجاد القيمة الدنيا
const min = Math.min(...numbers);
console.log(min); // 1
// الطريقة القديمة باستخدام apply
const oldMax = Math.max.apply(null, numbers);
console.log(oldMax); // 19
// النشر يعمل مع اي دالة تأخذ معاملات متعددة
function calculateTotal(price, tax, shipping) {
return price + tax + shipping;
}
const costs = [49.99, 4.50, 5.99];
const total = calculateTotal(...costs);
console.log(total); // 60.48
// يمكنك مزج النشر مع المعاملات العادية
const moreCosts = [4.50, 5.99];
const totalMixed = calculateTotal(49.99, ...moreCosts);
console.log(totalMixed); // 60.48
عامل النشر مع الكائنات
وسع ES2018 صيغة النشر لتعمل مع الكائنات. نشر كائن ينسخ جميع خصائصه المعددة الخاصة الى كائن جديد. هذا مفيد للغاية لنسخ الكائنات ودمجها وانشاء نسخ معدلة من الكائنات دون تغيير الاصلية.
نسخ الكائنات
تماما كما مع المصفوفات نشر كائن داخل قوسي كائن جديد ينشئ نسخة سطحية. هذا يعادل وظيفيا Object.assign({}, original) لكن صيغة النشر اكثر اختصارا وسهولة في القراءة.
مثال: نسخ كائن باستخدام النشر
const userProfile = {
name: 'Sarah',
email: 'sarah@example.com',
role: 'developer',
active: true
};
const profileCopy = { ...userProfile };
console.log(profileCopy);
// { name: 'Sarah', email: 'sarah@example.com', role: 'developer', active: true }
// النسخة كائن جديد
console.log(userProfile === profileCopy); // false
// تعديل النسخة لا يؤثر على الاصلية
profileCopy.role = 'senior developer';
console.log(userProfile.role); // 'developer'
console.log(profileCopy.role); // 'senior developer'
دمج الكائنات
نشر عدة كائنات داخل كائن واحد يدمج خصائصها. عندما يكون لكائنين نفس اسم الخاصية يفوز الاخير. هذا السلوك المعتمد على الترتيب متوقع ومفيد جدا لدمج كائنات التكوين والقيم الافتراضية وتفضيلات المستخدم.
مثال: دمج الكائنات
const defaultSettings = {
theme: 'light',
fontSize: 14,
language: 'en',
notifications: true,
autoSave: false
};
const userSettings = {
theme: 'dark',
fontSize: 18,
autoSave: true
};
// اعدادات المستخدم تتجاوز الافتراضية
const finalSettings = { ...defaultSettings, ...userSettings };
console.log(finalSettings);
// {
// theme: 'dark', -- تجاوزتها userSettings
// fontSize: 18, -- تجاوزتها userSettings
// language: 'en', -- بقيت من defaultSettings
// notifications: true, -- بقيت من defaultSettings
// autoSave: true -- تجاوزتها userSettings
// }
// دمج ثلاثي
const adminOverrides = { notifications: false, debugMode: true };
const adminSettings = { ...defaultSettings, ...userSettings, ...adminOverrides };
console.log(adminSettings);
// { theme: 'dark', fontSize: 18, language: 'en', notifications: false, autoSave: true, debugMode: true }
تجاوز خصائص محددة
نمط شائع جدا هو نشر كائن موجود ثم تجاوز خاصية او اكثر. هذا ينشئ كائنا جديدا بجميع الخصائص الاصلية بالاضافة الى تغييراتك دون تغيير الاصلي. هذا هو اساس تحديثات الحالة غير المتغيرة في اطر العمل مثل React.
مثال: تجاوز الخصائص
const product = {
id: 101,
name: 'Wireless Headphones',
price: 79.99,
inStock: true,
category: 'electronics'
};
// انشاء نسخة محدثة بسعر جديد
const updatedProduct = { ...product, price: 59.99 };
console.log(updatedProduct);
// { id: 101, name: 'Wireless Headphones', price: 59.99, inStock: true, category: 'electronics' }
// اضافة خصائص جديدة مع النسخ
const enrichedProduct = {
...product,
discount: 0.25,
finalPrice: product.price * 0.75,
tags: ['audio', 'wireless', 'bluetooth']
};
console.log(enrichedProduct.finalPrice); // 59.9925
// الاصلي لم يتغير
console.log(product.price); // 79.99
console.log(product.discount); // undefined
النشر مع النصوص
النصوص قابلة للتكرار في JavaScript لذا يمكنك نشر نص داخل مصفوفة للحصول على مصفوفة من الاحرف الفردية. هذا بديل نظيف لـ String.prototype.split('') ويعمل بشكل صحيح مع معظم احرف Unicode.
مثال: نشر النصوص
const greeting = 'Hello';
const chars = [...greeting];
console.log(chars); // ['H', 'e', 'l', 'l', 'o']
// مفيد لمعالجة النصوص
const word = 'JavaScript';
const reversed = [...word].reverse().join('');
console.log(reversed); // 'tpircSavaJ'
// عد الاحرف الفريدة
const sentence = 'hello world';
const uniqueChars = [...new Set(sentence)];
console.log(uniqueChars);
// ['h', 'e', 'l', 'o', ' ', 'w', 'r', 'd']
// يعمل مع رموز Unicode التعبيرية
const emoji = 'Hello! 🌍🚀';
const emojiChars = [...emoji];
console.log(emojiChars);
// ['H', 'e', 'l', 'l', 'o', '!', ' ', '🌍', '🚀']
// ملاحظة: split('') ستكسر الرموز متعددة البايت الى احرف غير مقروءة
معاملات التجميع في الدوال
صيغة معامل التجميع تبدو مطابقة لعامل النشر -- ثلاث نقاط متبوعة باسم -- لكنها تفعل العكس. بدلا من توسيع العناصر فهي تجمعها. عند استخدامها في قائمة معاملات الدالة يجمع معامل التجميع جميع المعاملات المتبقية في مصفوفة واحدة. هذا اكثر مرونة وحداثة من كائن arguments القديم.
مثال: معاملات التجميع الاساسية
// معامل التجميع يجمع كل المعاملات في مصفوفة
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
console.log(sum(5)); // 5
console.log(sum()); // 0
// معامل التجميع مصفوفة حقيقية (على عكس arguments)
function showType(...args) {
console.log(Array.isArray(args)); // true
console.log(args.map); // function map() { ... }
}
showType(1, 2, 3);
الجمع بين المعاملات العادية ومعامل التجميع
يمكنك استخدام معاملات عادية قبل معامل التجميع لالتقاط معاملات محددة بينما يجمع معامل التجميع كل شيء اخر. يجب ان يكون معامل التجميع دائما المعامل الاخير في توقيع الدالة. هذا النمط مفيد جدا عندما يكون للمعاملات الاولى معان محددة لكن العدد المتبقي متغير.
مثال: التجميع مع معاملات قيادية
function createTeam(teamName, captain, ...members) {
console.log(`Team: ${teamName}`);
console.log(`Captain: ${captain}`);
console.log(`Members: ${members.join(', ')}`);
console.log(`Total size: ${members.length + 1}`);
}
createTeam('Alpha', 'Alice', 'Bob', 'Charlie', 'Diana');
// Team: Alpha
// Captain: Alice
// Members: Bob, Charlie, Diana
// Total size: 4
// دالة تسجيل مع مستوى الخطورة
function log(level, ...messages) {
const timestamp = new Date().toISOString();
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
messages.forEach(msg => console.log(`${prefix} ${msg}`));
}
log('info', 'Server started', 'Listening on port 3000');
// [2025-01-15T10:30:00.000Z] [INFO] Server started
// [2025-01-15T10:30:00.000Z] [INFO] Listening on port 3000
log('error', 'Database connection failed');
// [2025-01-15T10:30:00.000Z] [ERROR] Database connection failed
function foo(...rest, lastParam) ستطلق SyntaxError. كما لا يمكن ان يكون هناك سوى معامل تجميع واحد لكل دالة.معاملات التجميع مقابل كائن Arguments
قبل وجود معاملات التجميع وفر JavaScript كائن arguments داخل كل دالة. بينما لا يزال arguments يعمل فان معاملات التجميع متفوقة من جميع النواحي. كائن arguments ليس مصفوفة حقيقية لذا لا يمكنك استخدام طرق المصفوفة مثل map وfilter او reduce مباشرة عليه. معاملات التجميع تعطيك مصفوفة حقيقية من البداية.
مثال: معاملات التجميع مقابل Arguments
// الطريقة القديمة: كائن arguments (تجنب هذا)
function oldSum() {
// arguments يشبه المصفوفة لكنه ليس مصفوفة حقيقية
console.log(Array.isArray(arguments)); // false
// يجب التحويل الى مصفوفة اولا
const args = Array.from(arguments);
return args.reduce((total, num) => total + num, 0);
}
// الطريقة الحديثة: معاملات التجميع (استخدم هذا)
function modernSum(...numbers) {
// numbers مصفوفة حقيقية
console.log(Array.isArray(numbers)); // true
return numbers.reduce((total, num) => total + num, 0);
}
console.log(oldSum(1, 2, 3)); // 6
console.log(modernSum(1, 2, 3)); // 6
// دوال السهم ليس لديها كائن arguments
// معاملات التجميع هي الخيار الوحيد
const arrowSum = (...numbers) => numbers.reduce((a, b) => a + b, 0);
console.log(arrowSum(10, 20, 30)); // 60
التجميع في التفكيك
صيغة التجميع تعمل ايضا داخل تعيينات التفكيك لكل من المصفوفات والكائنات. عند التفكيك يجمع عنصر التجميع جميع العناصر المتبقية التي لم تعين صراحة لمتغيرات فردية. هذا نمط قوي لفصل العناصر الاولى عن البقية او لاستخراج خصائص محددة مع ابقاء الاخرى مجمعة معا.
تفكيك المصفوفات مع التجميع
مثال: التجميع في تفكيك المصفوفات
const scores = [95, 88, 76, 92, 85, 71, 90];
// الحصول على اعلى درجتين وتجميع الباقي
const [highest, secondHighest, ...remaining] = scores;
console.log(highest); // 95
console.log(secondHighest); // 88
console.log(remaining); // [76, 92, 85, 71, 90]
// مفيد لنمط الراس والذيل
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// تخطي العناصر مع التجميع
const [first, , third, ...others] = ['a', 'b', 'c', 'd', 'e'];
console.log(first); // 'a'
console.log(third); // 'c'
console.log(others); // ['d', 'e']
تفكيك الكائنات مع التجميع
مثال: التجميع في تفكيك الكائنات
const user = {
id: 42,
name: 'Alex',
email: 'alex@example.com',
role: 'admin',
department: 'engineering',
hireDate: '2023-01-15'
};
// استخراج خصائص محددة وتجميع الباقي
const { id, name, ...details } = user;
console.log(id); // 42
console.log(name); // 'Alex'
console.log(details);
// { email: 'alex@example.com', role: 'admin', department: 'engineering', hireDate: '2023-01-15' }
// استخدام عملي: ازالة خاصية من كائن بشكل غير متغير
const { role, ...userWithoutRole } = user;
console.log(userWithoutRole);
// { id: 42, name: 'Alex', email: 'alex@example.com', department: 'engineering', hireDate: '2023-01-15' }
// role تم استبعاده دون تغيير الاصلي
// استخراج البيانات الحساسة
const apiResponse = {
data: { items: [1, 2, 3] },
status: 200,
headers: { 'content-type': 'application/json' },
config: { timeout: 5000 },
request: { method: 'GET' }
};
const { data, status, ...internals } = apiResponse;
console.log(data); // { items: [1, 2, 3] }
console.log(status); // 200
console.log(internals); // { headers: {...}, config: {...}, request: {...} }
delete obj.prop (الذي يغير الكائن) فكك مع التجميع: const { propToRemove, ...objectWithoutProp } = originalObject;. هذا نمط شائع في مخفضات Redux وادارة حالة React.النشر مقابل التجميع: نفس الصيغة غرض مختلف
النقاط الثلاث (...) لها سلوكان متعاكسان تماما حسب السياق. فهم الفرق حاسم لقراءة وكتابة JavaScript الحديث. التمييز الاساسي بسيط: النشر يوسع والتجميع يجمع.
مثال: النشر مقابل التجميع جنبا الى جنب
// النشر: يوسع مصفوفة الى عناصر فردية
// يستخدم في قوالب المصفوفات وقوالب الكائنات واستدعاءات الدوال
const parts = [3, 4, 5];
const complete = [1, 2, ...parts, 6]; // نشر: توسيع parts
console.log(complete); // [1, 2, 3, 4, 5, 6]
Math.max(...parts); // نشر: التوسيع الى معاملات
// يعادل Math.max(3, 4, 5)
// التجميع: يجمع العناصر الفردية في مصفوفة
// يستخدم في معاملات الدوال وانماط التفكيك
function demo(first, ...rest) { // تجميع: جمع المعاملات
console.log(first); // قيمة فردية
console.log(rest); // مصفوفة القيم المتبقية
}
demo(1, 2, 3, 4); // first=1, rest=[2, 3, 4]
const [a, ...others] = [10, 20, 30]; // تجميع: جمع الباقي
console.log(a); // 10
console.log(others); // [20, 30]
// كلاهما في سطر واحد
function merge(...arrays) { // تجميع: جمع كل المعاملات
return [].concat(...arrays); // نشر: توسيع كل مصفوفة
}
console.log(merge([1, 2], [3, 4], [5])); // [1, 2, 3, 4, 5]
... على الجانب الايمن من التعيين او داخل استدعاء دالة فهي نشر (توسيع). اذا ظهرت على الجانب الايسر من التعيين او في تعريف معامل دالة فهي تجميع (جمع).الانماط العملية
الان بعد فهمك لكل من النشر والتجميع دعنا نستكشف انماطا عملية تجمع هاتين الميزتين لحل مشاكل حقيقية. تظهر هذه الانماط باستمرار في قواعد اكواد JavaScript الحديثة والمكتبات واطر العمل.
تحديثات المصفوفات غير المتغيرة
عند العمل مع الحالة في اطر عمل مثل React يجب الا تغير المصفوفات مباشرة ابدا. عامل النشر يمنحك ادوات لاضافة وازالة وتحديث العناصر في المصفوفات دون تغيير.
مثال: عمليات المصفوفات غير المتغيرة
const todos = [
{ id: 1, text: 'Learn HTML', done: true },
{ id: 2, text: 'Learn CSS', done: true },
{ id: 3, text: 'Learn JavaScript', done: false }
];
// اضافة عنصر في النهاية
const withNewTodo = [...todos, { id: 4, text: 'Learn React', done: false }];
// اضافة عنصر في البداية
const withFirstTodo = [{ id: 0, text: 'Setup environment', done: true }, ...todos];
// ازالة عنصر بالمعرف (filter لا يغير)
const withoutSecond = todos.filter(todo => todo.id !== 2);
// تحديث عنصر محدد دون تغيير
const withUpdated = todos.map(todo =>
todo.id === 3
? { ...todo, done: true } // نشر + تجاوز
: todo
);
console.log(withUpdated[2]);
// { id: 3, text: 'Learn JavaScript', done: true }
// الادراج في فهرس محدد
const index = 2;
const withInserted = [
...todos.slice(0, index),
{ id: 99, text: 'Learn TypeScript', done: false },
...todos.slice(index)
];
console.log(withInserted.map(t => t.text));
// ['Learn HTML', 'Learn CSS', 'Learn TypeScript', 'Learn JavaScript']
تحديثات الكائنات غير المتغيرة
نفس مبدا عدم التغيير ينطبق على الكائنات. النشر يتيح لك انشاء نسخ جديدة من الكائنات بخصائص محدثة وهو جوهر ادارة الحالة في تطوير الواجهات الامامية الحديثة.
مثال: عمليات الكائنات غير المتغيرة
const state = {
user: { name: 'Alex', email: 'alex@example.com' },
theme: 'dark',
notifications: { email: true, push: false, sms: false },
lastLogin: '2025-01-15'
};
// تحديث خاصية على المستوى الاعلى
const newState1 = { ...state, theme: 'light' };
// تحديث كائن متداخل (يجب النشر في كل مستوى)
const newState2 = {
...state,
notifications: {
...state.notifications,
push: true
}
};
console.log(newState2.notifications);
// { email: true, push: true, sms: false }
// تحديث خاصية متداخلة بعمق
const newState3 = {
...state,
user: {
...state.user,
email: 'newemail@example.com'
}
};
console.log(newState3.user.email); // 'newemail@example.com'
console.log(state.user.email); // 'alex@example.com' -- لم يتغير
اغلفة الدوال والمزينات
معاملات التجميع والنشر يعملان معا بشكل جميل في اغلفة الدوال. يمكنك جمع جميع المعاملات بالتجميع وتمريرها لدالة اخرى بالنشر واضافة سلوك اضافي حول الاستدعاء. هذا هو النمط وراء مزينات التسجيل ومؤقتات الاداء ومنطق اعادة المحاولة والبرمجيات الوسيطة.
مثال: اغلفة الدوال
// غلاف توقيت يقيس وقت التنفيذ
function withTiming(fn) {
return function (...args) { // تجميع: جمع كل المعاملات
const start = performance.now();
const result = fn(...args); // نشر: تمرير كل المعاملات
const end = performance.now();
console.log(`${fn.name} took ${(end - start).toFixed(2)}ms`);
return result;
};
}
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const timedFib = withTiming(fibonacci);
timedFib(30); // fibonacci took 12.45ms (مثال)
// غلاف اعادة المحاولة للدوال غير المتزامنة
function withRetry(fn, maxRetries = 3) {
return async function (...args) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn(...args);
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Attempt ${attempt} failed, retrying...`);
}
}
};
}
async function fetchData(url, options) {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
const resilientFetch = withRetry(fetchData, 3);
// غلاف التخزين المؤقت
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit!');
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const memoizedAdd = memoize((a, b) => a + b);
console.log(memoizedAdd(1, 2)); // 3
console.log(memoizedAdd(1, 2)); // Cache hit! 3
امثلة من الواقع
دعنا نجمع كل شيء معا مع امثلة واقعية شاملة من المحتمل ان تواجهها في تطبيقات JavaScript الانتاجية.
بناء عميل API مرن
مثال: عميل API مع النشر والتجميع
// تكوين افتراضي يمكن تجاوزه
const defaultConfig = {
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
function createApiClient(customConfig = {}) {
// دمج التكوينات مع تجاوز المخصص للافتراضي
const config = {
...defaultConfig,
...customConfig,
headers: {
...defaultConfig.headers,
...customConfig.headers
}
};
return {
async get(endpoint, ...queryParams) {
const queryString = queryParams
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
const url = `${config.baseURL}${endpoint}?${queryString}`;
console.log(`GET ${url}`);
},
async post(endpoint, data, ...middlewares) {
let processedData = { ...data };
for (const middleware of middlewares) {
processedData = middleware(processedData);
}
console.log(`POST ${config.baseURL}${endpoint}`, processedData);
}
};
}
const api = createApiClient({
headers: { 'Authorization': 'Bearer token123' }
});
api.get('/users', ['page', '1'], ['limit', '10']);
// GET https://api.example.com/users?page=1&limit=10
const addTimestamp = (data) => ({ ...data, timestamp: Date.now() });
const addVersion = (data) => ({ ...data, version: '1.0' });
api.post('/users', { name: 'Alex' }, addTimestamp, addVersion);
// POST https://api.example.com/users { name: 'Alex', timestamp: 1705312200000, version: '1.0' }
دوال مساعدة للمصفوفات والكائنات
مثال: دوال مساعدة تستخدم النشر والتجميع
// ازالة التكرارات من عدة مصفوفات
function uniqueMerge(...arrays) {
return [...new Set(arrays.flat())];
}
console.log(uniqueMerge([1, 2, 3], [2, 3, 4], [4, 5, 6]));
// [1, 2, 3, 4, 5, 6]
// اختيار خصائص محددة من كائن
function pick(obj, ...keys) {
return keys.reduce((result, key) => {
if (key in obj) {
result[key] = obj[key];
}
return result;
}, {});
}
const fullUser = { id: 1, name: 'Alex', email: 'a@b.com', password: 'secret', role: 'admin' };
const safeUser = pick(fullUser, 'id', 'name', 'email');
console.log(safeUser); // { id: 1, name: 'Alex', email: 'a@b.com' }
// حذف خصائص محددة من كائن
function omit(obj, ...keys) {
const keysToRemove = new Set(keys);
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keysToRemove.has(key))
);
}
const publicUser = omit(fullUser, 'password', 'role');
console.log(publicUser); // { id: 1, name: 'Alex', email: 'a@b.com' }
// تركيب عدة دوال في واحدة
function compose(...fns) {
return function (value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
const double = x => x * 2;
const addOne = x => x + 1;
const square = x => x * x;
const transform = compose(square, addOne, double);
console.log(transform(3)); // square(addOne(double(3))) = square(7) = 49
// تقسيم مصفوفة الى مجموعتين
function partition(array, predicate) {
return array.reduce(
([pass, fail], item) =>
predicate(item)
? [[...pass, item], fail]
: [pass, [...fail, item]],
[[], []]
);
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const [evens, odds] = partition(numbers, n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
console.log(odds); // [1, 3, 5, 7, 9]
انماط معالجة الاحداث
مثال: نظام احداث مع النشر والتجميع
class EventEmitter {
constructor() {
this.listeners = {};
}
on(event, ...callbacks) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event] = [...this.listeners[event], ...callbacks];
return this;
}
emit(event, ...args) {
const handlers = this.listeners[event] || [];
handlers.forEach(handler => handler(...args));
return this;
}
off(event, callback) {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event]
.filter(cb => cb !== callback);
}
return this;
}
}
const emitter = new EventEmitter();
emitter.on('userLogin',
(user) => console.log(`Welcome, ${user.name}!`),
(user) => console.log(`Last login: ${user.lastLogin}`)
);
emitter.emit('userLogin', { name: 'Alex', lastLogin: '2025-01-14' });
// Welcome, Alex!
// Last login: 2025-01-14
null في مصفوفة ستطلق TypeError. تأكد دائما ان القيمة التي تنشرها قابلة للتكرار (مصفوفات ونصوص و Sets و Maps) او كائن (للنشر في الكائنات). استخدم نمط احتياطي مثل [...(items || [])] او { ...(config || {}) } عندما قد تكون القيمة null او undefined.تمرين عملي
ابن وحدة صغيرة لادارة المهام باستخدام عاملي النشر والتجميع فقط لجميع تحويلات البيانات. انشئ دالة createTaskManager تعيد كائنا بالطرق التالية: addTasks(...newTasks) تقبل اي عدد من كائنات المهام وتضيفها للقائمة الداخلية بشكل غير متغير، completeTask(id) تعلم مهمة كمنجزة دون تغيير المصفوفة الاصلية، removeTasks(...ids) تزيل عدة مهام بمعرفاتها، getStats() تعيد كائنا بالاجمالي والمنجز والمعلق، وmergeLists(...taskLists) تدمج عدة مصفوفات مهام وتزيل التكرارات بالمعرف. استخدم عامل النشر لجميع نسخ المصفوفات والكائنات واستخدم معاملات التجميع لقوائم المعاملات متغيرة الطول واستخدم التفكيك مع التجميع لاستخراج وفصل البيانات ولا تستخدم ابدا push او splice او delete او اي طريقة تغيير. اختبر وحدتك بانشاء مهام واكمال بعضها وازالة اخرى ودمج قوائم متعددة والتحقق من ان البيانات الاصلية لم تتغير ابدا.