We are still cooking the magic in the way!
JavaScript المتقدم (ES6+)
معاملات الباقي والمعاملات
معاملات الباقي والمعاملات
تسمح معاملات الباقي لك بتمثيل عدد غير محدد من المعاملات كمصفوفة. توفر هذه الميزة من ES6+ طريقة نظيفة وحديثة لإنشاء دوال متغيرة (دوال تقبل أي عدد من المعاملات)، لتحل محل كائن arguments القديم.
معاملات الباقي الأساسية
تستخدم معاملات الباقي صيغة ... لجمع جميع المعاملات المتبقية في مصفوفة حقيقية:
// معاملات الباقي الأساسية
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum()); // 0 (مصفوفة فارغة)
// معاملات الباقي هي مصفوفات حقيقية
function logArgs(...args) {
console.log(Array.isArray(args)); // true
console.log(args.length); // عدد المعاملات
// يمكن استخدام دوال المصفوفة
args.forEach((arg, index) => {
console.log(`Argument ${index}: ${arg}`);
});
}
logArgs('a', 'b', 'c');
// true
// 3
// Argument 0: a
// Argument 1: b
// Argument 2: c
نصيحة: تنشئ معاملات الباقي مصفوفة حقيقية، على عكس كائن
arguments. هذا يعني أنه يمكنك استخدام جميع دوال المصفوفة مثل map و filter و reduce وغيرها مباشرة بدون تحويل.
دمج الباقي مع المعاملات العادية
يمكنك مزج المعاملات العادية مع معاملات الباقي. يجب أن يكون معامل الباقي دائماً آخر معامل:
// المعامل الأول منفصل، الباقي يتم جمعها
function greet(greeting, ...names) {
return `${greeting}, ${names.join(' and ')}!`;
}
console.log(greet('Hello', 'Alice')); // "Hello, Alice!"
console.log(greet('Hello', 'Alice', 'Bob', 'Charlie'));
// "Hello, Alice and Bob and Charlie!"
// معاملات عادية متعددة، ثم الباقي
function calculate(operation, multiplier, ...numbers) {
if (operation === 'multiply') {
return numbers.map(n => n * multiplier);
}
return numbers;
}
console.log(calculate('multiply', 2, 1, 2, 3, 4));
// [2, 4, 6, 8]
مهم: يجب أن تكون معاملات الباقي آخر معامل في توقيع الدالة. لا يمكن أن يكون لديك أي معاملات بعد معامل الباقي.
function bad(...rest, last) {} غير صالح!
معاملات الباقي مقابل كائن Arguments
قبل ES6، كنا نستخدم كائن arguments. معاملات الباقي هي بديل أفضل بكثير:
// الطريقة القديمة - كائن arguments
function oldSum() {
// arguments يشبه المصفوفة، لكنه ليس مصفوفة حقيقية
console.log(Array.isArray(arguments)); // false
// نحتاج إلى التحويل إلى مصفوفة أولاً
const args = Array.prototype.slice.call(arguments);
return args.reduce((total, num) => total + num, 0);
}
// طريقة ES6+ - معاملات الباقي
function newSum(...numbers) {
// مصفوفة حقيقية بالفعل!
console.log(Array.isArray(numbers)); // true
return numbers.reduce((total, num) => total + num, 0);
}
console.log(oldSum(1, 2, 3)); // 6
console.log(newSum(1, 2, 3)); // 6
// كائن arguments لديه مشاكل
function problematic() {
// الدوال السهمية ليس لديها arguments
const arrow = () => {
console.log(arguments); // يستخدم arguments الأب (مربك!)
};
arrow();
}
الفروقات الرئيسية:
argumentsيشبه المصفوفة لكنه ليس مصفوفة حقيقيةargumentsيحتوي على جميع المعاملات، الباقي يجمع فقط المتبقيةargumentsلا يعمل في الدوال السهمية- معاملات الباقي لديها أداء أفضل ونية أوضح
الدوال المتغيرة
تجعل معاملات الباقي إنشاء دوال مرنة أسهل بكثير:
// مسجل يقبل أي عدد من الرسائل
function log(level, ...messages) {
const timestamp = new Date().toISOString();
const formatted = messages.join(' ');
console.log(`[${timestamp}] [${level}] ${formatted}`);
}
log('INFO', 'User', 'logged', 'in');
// [2024-02-08T10:30:00.000Z] [INFO] User logged in
log('ERROR', 'Connection', 'failed');
// [2024-02-08T10:30:01.000Z] [ERROR] Connection failed
// العمليات الرياضية
function max(...numbers) {
if (numbers.length === 0) return -Infinity;
return Math.max(...numbers);
}
console.log(max(1, 5, 3, 9, 2)); // 9
// دمج النصوص مع فاصل
function joinWith(separator, ...strings) {
return strings.join(separator);
}
console.log(joinWith(' - ', 'apple', 'banana', 'orange'));
// "apple - banana - orange"
حالات استخدام معاملات الباقي
// 1. بناء أغلفة API
function apiCall(endpoint, method = 'GET', ...params) {
const queryString = params
.map(p => `${p.key}=${p.value}`)
.join('&');
return `${method} ${endpoint}?${queryString}`;
}
console.log(
apiCall('/users', 'GET',
{ key: 'page', value: 1 },
{ key: 'limit', value: 10 }
)
);
// "GET /users?page=1&limit=10"
// 2. إنشاء مصفوفات بأنماط محددة
function createRange(start, end, ...excludes) {
const range = [];
for (let i = start; i <= end; i++) {
if (!excludes.includes(i)) {
range.push(i);
}
}
return range;
}
console.log(createRange(1, 10, 3, 5, 7));
// [1, 2, 4, 6, 8, 9, 10]
// 3. نمط باعث الأحداث
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => {
callback(...args); // نشر args إلى callback
});
}
}
}
const emitter = new EventEmitter();
emitter.on('data', (a, b, c) => console.log('Received:', a, b, c));
emitter.emit('data', 1, 2, 3); // Received: 1 2 3
معاملات الباقي مع التفكيك
يمكنك دمج معاملات الباقي مع التفكيك لأنماط قوية:
// الباقي في تفكيك المصفوفة داخل المعاملات
function processData([first, second, ...rest]) {
console.log('First:', first);
console.log('Second:', second);
console.log('Rest:', rest);
}
processData([1, 2, 3, 4, 5]);
// First: 1
// Second: 2
// Rest: [3, 4, 5]
// الباقي في تفكيك الكائن داخل المعاملات
function createUser({ name, email, ...metadata }) {
return {
credentials: { name, email },
metadata: metadata
};
}
console.log(createUser({
name: 'Alice',
email: 'alice@example.com',
age: 30,
city: 'New York',
country: 'USA'
}));
// {
// credentials: { name: 'Alice', email: 'alice@example.com' },
// metadata: { age: 30, city: 'New York', country: 'USA' }
// }
أمثلة عملية
// 1. دالة تنسيق مرنة
function format(template, ...values) {
return template.replace(/\{\d+\}/g, (match) => {
const index = match.slice(1, -1);
return values[index] !== undefined ? values[index] : match;
});
}
console.log(format('Hello {0}, you have {1} messages', 'Alice', 5));
// "Hello Alice, you have 5 messages"
// 2. التطبيق الجزئي
function multiply(multiplier, ...numbers) {
return numbers.map(n => n * multiplier);
}
const double = (...nums) => multiply(2, ...nums);
const triple = (...nums) => multiply(3, ...nums);
console.log(double(1, 2, 3)); // [2, 4, 6]
console.log(triple(1, 2, 3)); // [3, 6, 9]
// 3. تكوين الدوال
function compose(...functions) {
return function(initialValue) {
return functions.reduceRight(
(value, fn) => fn(value),
initialValue
);
};
}
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const compute = compose(square, double, addOne);
console.log(compute(2)); // ((2 + 1) * 2)^2 = 36
// 4. التحقق من البيانات
function validateRequired(fieldName, ...validators) {
return function(value) {
const errors = [];
if (value === undefined || value === null || value === '') {
errors.push(`${fieldName} is required`);
return errors;
}
for (const validator of validators) {
const error = validator(value);
if (error) errors.push(error);
}
return errors.length > 0 ? errors : null;
};
}
const minLength = min => value =>
value.length < min ? `Must be at least ${min} characters` : null;
const hasNumber = value =>
/\d/.test(value) ? null : 'Must contain a number';
const validatePassword = validateRequired(
'Password',
minLength(8),
hasNumber
);
console.log(validatePassword('abc')); // ["Must be at least 8 characters", "Must contain a number"]
console.log(validatePassword('abcd1234')); // null (صالح)
معاملات الباقي مع القيم الافتراضية
// معاملات الباقي لها قيمة افتراضية لمصفوفة فارغة
function collect(...items) {
console.log(items); // دائماً مصفوفة، حتى لو كانت فارغة
}
collect(); // []
collect(1, 2, 3); // [1, 2, 3]
// دمج المعاملات الافتراضية مع الباقي
function createList(title = 'Untitled', ...items) {
return {
title,
items,
count: items.length
};
}
console.log(createList());
// { title: 'Untitled', items: [], count: 0 }
console.log(createList('Shopping', 'Milk', 'Bread', 'Eggs'));
// { title: 'Shopping', items: ['Milk', 'Bread', 'Eggs'], count: 3 }
اعتبارات الأداء
الأداء: معاملات الباقي محسّنة للغاية في محركات JavaScript الحديثة. إنها بشكل عام أسرع من التعامل يدوياً مع كائن
arguments وأكثر كفاءة في الذاكرة.
الأخطاء الشائعة
// ❌ يجب أن يكون معامل الباقي الأخير
function bad(...rest, last) {
// SyntaxError: Rest parameter must be last formal parameter
}
// ✅ الموضع الصحيح
function good(first, ...rest) {
console.log(first, rest);
}
// ❌ يُسمح بمعامل باقي واحد فقط
function alsoBad(...rest1, ...rest2) {
// SyntaxError
}
// ❌ لا تستخدم arguments مع الباقي
function unnecessary(...args) {
console.log(arguments); // لا تفعل هذا - استخدم args!
}
// ✅ فقط استخدم معامل الباقي
function better(...args) {
console.log(args); // نظيف وواضح
}
تمرين تطبيقي:
المهمة: أنشئ دوال مساعدة باستخدام معاملات الباقي:
- اكتب دالة
sumتجمع أي عدد من المعاملات - أنشئ دالة
filterتأخذ predicate وأي عدد من القيم - ابنِ دالة
curryتجمع المعاملات حتى يتم توفير ما يكفي
الحل:
// 1. دالة الجمع
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 2. دالة التصفية
function filter(predicate, ...values) {
return values.filter(predicate);
}
console.log(filter(x => x > 5, 1, 3, 6, 8, 4, 9));
// [6, 8, 9]
// 3. دالة Curry
function curry(fn, arity = fn.length, ...args) {
return (...newArgs) => {
const allArgs = [...args, ...newArgs];
if (allArgs.length >= arity) {
return fn(...allArgs);
}
return curry(fn, arity, ...allArgs);
};
}
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
الملخص
في هذا الدرس، تعلمت:
- معاملات الباقي تجمع المعاملات المتبقية في مصفوفة حقيقية
- استخدم صيغة
...nameفي معاملات الدالة - يجب أن تكون معاملات الباقي آخر معامل
- إنها تحل محل كائن
argumentsالقديم بوظائف أفضل - مثالية لإنشاء دوال متغيرة تقبل أي عدد من المعاملات
- ادمج مع التفكيك والمعاملات الافتراضية لأنماط قوية
التالي: في الدرس التالي، سنستكشف الدوال عالية الرتبة وأنماط البرمجة الوظيفية في JavaScript!