الفرق بين Rest و Spread في JavaScript: شرح عملي بالأمثلة
مقدمة: لماذا يختلط Rest مع Spread في JavaScript؟
تستخدم JavaScript ثلاث نقاط ... لتمثيل كلٍّ من Rest وSpread. ورغم التشابه في الشكل، فإن الاستخدام والنتيجة مختلفان تماماً. الفارق الجوهري هو أن Rest يجمع القيم المتبقية في Array أو في كائن خصائص بحسب السياق، بينما يعمل Spread على توسيع القيم القابلة للتكرار iterables إلى عناصر منفصلة.
إذا كنت تتعامل مع Functions أو Destructuring أو نسخ البيانات ودمجها، ففهم هذا الفرق ضروري لكتابة كود أوضح وأكثر احترافية.

الفرق السريع بين Rest و Spread
- Rest: يجمع العناصر أو القيم المتبقية داخل Array أو كائن خصائص وفق موضع الاستخدام.
- Spread: يفكّك القيم القابلة للتكرار ويحوّلها إلى عناصر منفصلة.
إليك مثالاً سريعاً على Rest:
// Use rest to enclose the rest of specific user-supplied values into an array:
function myBio ( firstName, lastName, ...otherInfo ) {
return otherInfo;
}
// Invoke myBio function while passing five arguments to its parameters:
myBio( "Oluwatobi" , "Sofela" , "CodeSweetly" , "Web Developer" , "Male" );
// The invocation above will return:
[ "CodeSweetly" , "Web Developer" , "Male" ]
في المثال السابق، قام ...otherInfo بجمع القيم المتبقية داخل Array.
والآن مثال على Spread:
// Define a function with three parameters:
function myBio ( firstName, lastName, company ) {
return `${firstName} ${lastName} runs ${company}`;
}
// Use spread to expand an array’s items into individual arguments:
myBio(...[ "Oluwatobi" , "Sofela" , "CodeSweetly" ]);
// The invocation above will return:
// "Oluwatobi Sofela runs CodeSweetly"
هنا استخدمنا Spread لتوزيع عناصر Array على معاملات الدالة بشكل منفصل.
ما هو Rest Operator في JavaScript؟
Rest Operator يُستخدم لتجميع ما تبقّى من القيم التي يمررها المستخدم داخل بنية واحدة. وغالباً ما تكون هذه البنية Array عند استخدامه في الدوال أو في Array Destructuring.
...yourValues
الثلاث نقاط ... هي صيغة Rest، وما بعدها هو اسم المتغير الذي سيستقبل القيم المتبقية.
مهم جداً: عند استخدامه داخل تعريف دالة، يجب أن يأتي Rest Parameter في آخر معامل فقط.
كيف يعمل Rest داخل Function؟
عند تعريف دالة، يمكن استعمال Rest قبل آخر معامل من أجل جمع عدد غير محدد من القيم.
// Define a function with two regular parameters and one rest parameter:
function myBio ( firstName, lastName, ...otherInfo ) {
return otherInfo;
}
في هذا المثال:
firstNameوlastNameمعاملان عاديان....otherInfoهو Rest Parameter.- أي قيم إضافية تُمرَّر بعد المعاملين الأولين ستُجمع داخل
otherInfo.
مثال تطبيقي:
// Define a function with two regular parameters and one rest parameter:
function myBio ( firstName, lastName, ...otherInfo ) {
return otherInfo;
}
// Invoke myBio function while passing five arguments to its parameters:
myBio( "Oluwatobi" , "Sofela" , "CodeSweetly" , "Web Developer" , "Male" );
// The invocation above will return:
[ "CodeSweetly" , "Web Developer" , "Male" ]
النتيجة هنا واضحة: أول قيمتين تم إسنادهما إلى firstName وlastName، بينما جُمعت القيم المتبقية في otherInfo.
تنبيه مهم: لا تستخدم “use strict” داخل دالة تحتوي على Rest Parameter
إذا وضعت "use strict" داخل دالة تحتوي على Rest Parameter أو Default Parameter أو Destructuring Parameter، فستحصل على SyntaxError.
// Define a function with one rest parameter:
function printMyName ( ...value ) {
"use strict";
return value;
}
// The definition above will return:
// "Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list"
الحل الصحيح هو وضع "use strict" خارج الدالة إذا كان هذا مناسباً لنطاق الملف أو السكربت بالكامل:
// Define a “use strict” directive outside your function:
"use strict";
// Define a function with one rest parameter:
function printMyName ( ...value ) {
return value;
}
// Invoke the printMyName function while passing two arguments to its parameters:
printMyName( "Oluwatobi" , "Sofela" );
// The invocation above will return:
[ "Oluwatobi" , "Sofela" ]
كيف يعمل Rest في Destructuring Assignment؟
لا يقتصر استخدام Rest على الدوال فقط، بل يمكن استخدامه أيضاً في Destructuring مع المصفوفات والكائنات.
Rest مع Array Destructuring
// Define a destructuring array with two regular variables and one rest variable:
const [firstName, lastName, ...otherInfo] = [
"Oluwatobi" , "Sofela" , "CodeSweetly" , "Web Developer" , "Male"
];
// Invoke the otherInfo variable:
console.log(otherInfo);
// The invocation above will return:
[ "CodeSweetly" , "Web Developer" , "Male" ]
في هذا السياق، يقوم Rest بتجميع العناصر المتبقية داخل Array.
Rest مع Object Destructuring
// Define a destructuring object with two regular variables and one rest variable:
const { firstName, lastName, ...otherInfo } = {
firstName: "Oluwatobi",
lastName: "Sofela",
companyName: "CodeSweetly",
profession: "Web Developer",
gender: "Male"
}
// Invoke the otherInfo variable:
console.log(otherInfo);
// The invocation above will return:
// { companyName: "CodeSweetly", profession: "Web Developer", gender: "Male" }
هنا النتيجة ليست Array، بل Object يحتوي على الخصائص المتبقية. وهذه نقطة مهمة:
- في Function وArray Destructuring يعيد Rest مصفوفة.
- في Object Destructuring يعيد كائن خصائص.
الفرق بين arguments و Rest Parameters
رغم أن كليهما يتعامل مع القيم الممررة إلى الدوال، إلا أن هناك فروقاً مهمة تجعل Rest Parameters الخيار الأحدث والأفضل في معظم الحالات.
1) الكائن arguments ليس Array حقيقية
الكائن arguments يشبه المصفوفة، لكنه ليس Array حقيقية. لذلك لا يمكنك الاعتماد عليه بنفس مرونة المصفوفات المعتادة.
أما Rest Parameter فهو Array حقيقية، وبالتالي يمكنك استخدام دوال مثل:
map()sort()forEach()pop()
2) لا يمكن استخدام arguments داخل Arrow Function
داخل Arrow Function لا يتوفر arguments بالشكل المعتاد، بينما يمكنك استخدام Rest دون مشكلة.
3) Rest هو الخيار الأنسب في ES6
إذا كنت تكتب كوداً حديثاً ومتوافقاً مع ES6، فمن الأفضل استخدام Rest Parameters بدلاً من arguments في أغلب السيناريوهات.
ما هو Spread Operator في JavaScript؟
Spread Operator يقوم بعكس فكرة Rest تقريباً. فبدلاً من جمع القيم، يقوم بتوسيع العناصر القابلة للتكرار iterables إلى قيم منفصلة.
يظهر استخدامه عادة في ثلاثة مواضع رئيسية:
- داخل Array Literals
- داخل Function Calls
- داخل Object Literals لنسخ الخصائص أو دمجها
أمثلة عملية على Spread Operator
المثال الأول: استخدام Spread داخل Array Literal
const myName = [ "Sofela" , "is" , "my" ];
const aboutMe = [ "Oluwatobi" , ...myName, "name." ];
console.log(aboutMe);
// The invocation above will return:
// [ "Oluwatobi" , "Sofela" , "is" , "my" , "name." ]
في هذا المثال، تم نسخ عناصر myName وتوسيعها داخل aboutMe.
إذا كانت القيم داخل المصفوفة من النوع primitive مثل النصوص والأرقام، فإن التعديل على الأصل لن ينعكس على النسخة بهذا الشكل المباشر.
المثال الثاني: تحويل String إلى عناصر منفصلة داخل Array
const myName = "Oluwatobi Sofela";
console.log([...myName]);
// The invocation above will return:
// [ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]
لأن String نوع قابل للتكرار iterable، يستطيع Spread تفكيكه إلى أحرف منفصلة.
المثال الثالث: استخدام Spread في Function Call
const numbers = [ 1 , 3 , 5 , 7 ];
function addNumbers ( a, b, c, d ) {
return a + b + c + d;
}
console.log(addNumbers(...numbers));
// The invocation above will return:
// 16
في هذا المثال، توسعت عناصر numbers لتملأ معاملات الدالة.
وإذا كانت المصفوفة تحتوي على عناصر أكثر من عدد المعاملات، فسيتم تجاهل العناصر الزائدة:
const numbers = [ 1 , 3 , 5 , 7 , 10 , 200 , 90 , 59 ];
function addNumbers ( a, b, c, d ) {
return a + b + c + d;
}
console.log(addNumbers(...numbers));
// The invocation above will return:
// 16
مثال إضافي يوضح أثر Spread مع النصوص والقيم المختلفة:
const myName = "Oluwatobi Sofela";
function spellName ( a, b, c ) {
return a + b + c;
}
console.log(spellName(...myName)); // returns: "Olu"
console.log(spellName(...myName[ 3 ])); // returns: "wundefinedundefined"
console.log(spellName([...myName])); // returns: "O,l,u,w,a,t,o,b,i, ,S,o,f,e,l,aundefinedundefined"
console.log(spellName({...myName})); // returns: "[object Object]undefinedundefined"
المثال الرابع: استخدام Spread داخل Object Literal
const myNames = [ "Oluwatobi" , "Sofela" ];
const bio = { ...myNames, runs : "codesweetly.com" };
console.log(bio);
// The invocation above will return:
// { 0 : "Oluwatobi" , 1 : "Sofela" , runs : "codesweetly.com" }
عند استخدام Spread داخل Object Literal مع Array، تتحول العناصر إلى خصائص مرقمة.
معلومات مهمة يجب معرفتها عن Spread Operator
1) Spread لا يوسّع قيم Object العادي لأنه ليس iterable
الكائن العادي Object ليس من الأنواع القابلة للتكرار iterable بشكل افتراضي، لذلك لا يمكنك توسيعه كما تفعل مع Array أو String في سياق يتطلب عناصر متكررة.
لكن يمكنك استخدامه لنسخ الخصائص من كائن إلى آخر:
const myName = { firstName : "Oluwatobi" , lastName : "Sofela" };
const bio = { ...myName, website : "codesweetly.com" };
console.log(bio);
// The invocation above will return:
// { firstName : "Oluwatobi" , lastName : "Sofela" , website : "codesweetly.com" };
الأنواع القابلة للتكرار افتراضياً تشمل:
- Array
- TypedArray
- String
- Map
- Set
2) الخصائص المتطابقة يتم استبدالها حسب الترتيب
عند دمج كائنات باستخدام Spread، فإن الخاصية التي تأتي لاحقاً هي التي تفوز.
const myName = { firstName : "Tobi" , lastName : "Sofela" };
const bio = { ...myName, firstName : "Oluwatobi" , website : "codesweetly.com" };
console.log(bio);
// The invocation above will return:
// { firstName : "Oluwatobi" , lastName : "Sofela" , website : "codesweetly.com" };
لاحظ أن firstName في bio استبدل القيمة القادمة من myName.
3) Spread ينفذ Shallow Copy وليس Deep Copy
هذه من أهم النقاط التقنية. عند استخدام Spread لنسخ Array أو Object، فإنه ينفذ Shallow Copy فقط، وليس Deep Copy.
مثال على القيم البدائية Primitive Values
const myName = [ "Sofela" , "is" , "my" ];
const aboutMe = [ "Oluwatobi" , ...myName, "name." ];
console.log(aboutMe);
// [ "Oluwatobi" , "Sofela" , "is" , "my" , "name." ]
ثم نضيف عنصراً إلى المصفوفة الأصلية:
myName.push( "real" );
console.log(myName); // ["Sofela", "is", "my", "real"]
console.log(aboutMe); // ["Oluwatobi", "Sofela", "is", "my", "name."]
لم تتأثر النسخة لأن العناصر هنا Primitive.
مثال على القيم غير البدائية Non-Primitive Values
const myName = [[ "Sofela" , "is" , "my" ]];
const aboutMe = [ "Oluwatobi" , ...myName, "name." ];
console.log(aboutMe);
// [ "Oluwatobi" , [ "Sofela" , "is" , "my" ], "name." ]
ثم نعدّل القيمة الداخلية:
myName[ 0 ].push( "real" );
console.log(myName); // [["Sofela", "is", "my", "real"]]
console.log(aboutMe); // ["Oluwatobi", ["Sofela", "is", "my", "real"], "name."]
هنا انعكس التعديل لأن العنصر المنسوخ نفسه كان مرجعاً إلى كائن/مصفوفة داخلية، وهذا هو معنى Shallow Copy.
مثال على Object يحتوي على قيم Primitive فقط
const myName = { firstName : "Oluwatobi" , lastName : "Sofela" };
const bio = { ...myName };
myName.firstName = "Tobi";
console.log(myName); // { firstName: "Tobi", lastName: "Sofela" }
console.log(bio); // { firstName: "Oluwatobi", lastName: "Sofela" }
لم تتأثر النسخة لأن الخصائص هنا بدائية فقط.
مثال على Object يحتوي على قيمة غير بدائية
const myName = { fullName : { firstName : "Oluwatobi" , lastName : "Sofela" } };
const bio = { ...myName };
myName.fullName.firstName = "Tobi";
console.log(myName); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }
console.log(bio); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }
هنا نلاحظ أن التعديل انعكس على النسخة، لأن fullName كائن داخلي تم نسخه سطحياً فقط.
متى تستخدم Rest ومتى تستخدم Spread؟
| الحالة | الأداة المناسبة | السبب |
|---|---|---|
| جمع معاملات إضافية داخل دالة | Rest | لأنه يجمع القيم المتبقية داخل Array |
| تفكيك Array إلى معاملات دالة | Spread | لأنه يوسّع العناصر إلى قيم منفصلة |
| استخراج جزء من القيم والباقي في متغير واحد | Rest | مفيد مع Destructuring |
| نسخ أو دمج Arrays وObjects | Spread | يوفر صيغة مختصرة وواضحة |
ملخص الفرق بين Rest و Spread في JavaScript
- Rest يجمع القيم المتبقية.
- Spread يوسّع القيم إلى عناصر منفصلة.
- كلاهما يستخدمان الرمز نفسه
.... - السياق هو الذي يحدد إن كانت الصيغة Rest أو Spread.
- Spread ينفذ Shallow Copy فقط.
- Rest Parameters أكثر مرونة وحداثة من
arguments.
الخلاصة التقنية
إذا أردت قاعدة بسيطة لتتذكر الفرق: Rest يجمع وSpread يوزّع. لكن في التطبيقات العملية، الأهم هو فهم السياق، خصوصاً عند التعامل مع Destructuring ونسخ الكائنات والمصفوفات. ومن الناحية التقنية، أكثر خطأ شائع يقع فيه المطورون هو الاعتقاد بأن Spread ينفذ Deep Copy، بينما هو في الحقيقة ينسخ سطحياً فقط. لذلك، عند التعامل مع بيانات متداخلة، يجب الانتباه جيداً للمراجع الداخلية حتى لا تظهر آثار جانبية غير متوقعة في التطبيق.