Menu
العربية

معامل Rest و Spread في JavaScript (...) بالتفصيل

تعرّف على طريقة عمل المعامل ... في JavaScript: كيف تجمع الوسائط باستخدام rest، وكيف توسّع المصفوفات والكائنات باستخدام spread، ومتى تستخدم كل واحد منهما.

نفس الصيغة، لكن بمهمتين مختلفتين

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

  • Rest: عند كتابة ...name في جهة الاستقبال، فإنها تجمع عدة قيم في مصفوفة أو كائن واحد.
  • Spread: عند كتابة ...value في جهة الإعطاء، فإنها تنشر عناصر المصفوفة أو الكائن إلى أجزائها المنفصلة.

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

معاملات Rest: تجميع وسائط الدالة

معامل rest داخل تعريف الدالة يجمع أي عدد من الوسائط الممررة إليها في مصفوفة حقيقية:

index.js
Output
Click Run to see the output here.

nums هنا مصفوفة عادية تمامًا. تقدر تستخدم معها .map و.filter، تتحقق من .length، تمررها لدالة ثانية… باختصار، كل ما تفعله المصفوفات متاح لك.

ويمكن دمج معاملات الـ rest مع المعاملات العادية، لكن بشرط واحد: لازم يكون معامل الـ rest في الآخر:

index.js
Output
Click Run to see the output here.

label يلتقط الوسيط الأول، وكل ما يأتي بعده يُجمع داخل items. وضع معامل rest في أي مكان غير الأخير يُسبّب خطأً في الصياغة (syntax error).

الفرق بين rest و الكائن arguments القديم

في الكود القديم بلغة JavaScript، كان المطورون يعتمدون على متغيّر سحري اسمه arguments داخل الدوال العادية. يبدو كأنه مصفوفة، لكنه في الحقيقة ليس مصفوفة، ولهذا لا تعمل عليه توابع المصفوفات مباشرةً. معاملات rest تحلّ هذه المشكلة بشكل أنيق:

index.js
Output
Click Run to see the output here.

الدوال السهمية (arrow functions) لا تمتلك أصلاً كائن arguments، لذا فإن rest parameters هي الطريقة الوحيدة لاستقبال عدد متغيّر من الوسائط فيها. اعتمد على ...args في أي كود جديد تكتبه.

معامل spread عند استدعاء الدوال

معامل spread يقوم بالعكس تماماً: يأخذ مصفوفة ويقوم بفكّها إلى وسائط منفصلة عند نقطة استدعاء الدالة.

index.js
Output
Click Run to see the output here.

Math.max يستقبل أرقامًا فردية، لا مصفوفة. قبل ظهور معامل spread، كنا نلجأ إلى Math.max.apply(null, nums). أما الآن، فيكفي وضع ... وانتهى الأمر.

لاحظ كيف أن نفس ... يمثّل rest عند تعريف الدالة، ويمثّل spread عند استدعائها — الموقع هو ما يحدد الدور.

استخدام spread داخل المصفوفات

توزيع القيم داخل مصفوفة جديدة يتيح لك نسخ مصفوفة أو دمج عدة مصفوفات معًا:

index.js
Output
Click Run to see the output here.

[...a] يعطيك مصفوفة جديدة بنفس العناصر، وهذا مفيد لما تحتاج ترتّب أو تعدّل بدون ما تمس المصفوفة الأصلية:

index.js
Output
Click Run to see the output here.

scores ما تغيّرت لأن .sort اشتغلت على النسخة. عادة بسيطة، لكن فايدتها كبيرة لما تكتب كود لازم يكون خالي من الآثار الجانبية المفاجئة.

استخدام spread مع الكائنات (Object Literals)

معامل spread يشتغل كمان مع الكائنات العادية، حيث يقوم بدمج خصائصها داخل كائن جديد:

index.js
Output
Click Run to see the output here.

المفاتيح الأخيرة هي الفائزة. قيمة updates.age تطغى على user.age، بينما يُضاف city كإضافة جديدة. ترتيب عمليات الـ spread هو ما يحدد النتيجة النهائية — خُذ هذا في الحسبان عندما تدمج القيم الافتراضية مع قيم التحديث:

index.js
Output
Click Run to see the output here.

الافتراضيات أولًا، ثم خيارات المستخدم. المستخدم يفوز في fontSize، ويرث theme.

فخ النسخ السطحي (Shallow Copy)

معامل spread ينسخ مستوى واحدًا فقط. الكائنات والمصفوفات المتداخلة تظل مشتركة بين الأصل والنسخة:

index.js
Output
Click Run to see the output here.

كلا المصفوفتين تظهران الوسم الجديد، لأن copy.tags و original.tags يشيران إلى نفس المصفوفة في الذاكرة. فمعامل spread لم ينسخ المصفوفة الداخلية، بل اكتفى بنسخ المرجع فقط.

وإذا أردت نسخة عميقة حقيقية لبيانات بسيطة، فاستخدم structuredClone:

index.js
Output
Click Run to see the output here.

أصبح لديك الآن مصفوفتان مستقلّتان تمامًا. الدالة structuredClone مدمجة في المتصفحات الحديثة وفي Node، وتتعامل مع الهياكل المتداخلة بكفاءة، وهي الخيار الأنسب متى ما كانت النسخة السطحية (shallow copy) غير كافية.

استخدام rest في التفكيك (Destructuring)

يعمل rest أيضًا داخل عمليات التفكيك، حيث يقوم بتجميع العناصر أو الخصائص المتبقية:

index.js
Output
Click Run to see the output here.

استخراج عدد من الحقول مع الإبقاء على الباقي داخل كائن واحد نمط شائع جدًا عند تمرير البروبس، أو حذف الحقول الحساسة، أو إنشاء نسخة معدّلة من البيانات:

index.js
Output
Click Run to see the output here.

password يتم استخلاصها (وتجاهلها)، بينما يحتفظ safe بكل ما تبقّى. بدون أي تعديل على الأصل، وبدون نسخ يدوي.

مراجعة سريعة

  • ...name داخل قائمة الوسائط أو داخل نمط التفكيك (destructuring) يعني rest: أي أنه يُجمِّع القيم.
  • ...value داخل استدعاء دالة أو مصفوفة أو كائن يعني spread: أي أنه يَنشر القيم.
  • النسخ عبر spread هو نسخ سطحي (shallow copy). البُنى المتداخلة تبقى مُشتركة بين الأصل والنسخة. استخدم structuredClone إذا احتجت نسخة عميقة.
  • معاملات rest هي مصفوفات حقيقية — استعملها بدل arguments القديم.
  • في الكائنات، عمليات spread المتأخرة تطغى على السابقة، وهذه هي الطريقة المعتادة لبناء نمط "قيم افتراضية + تخصيصات".

التالي: الإغلاقات (Closures)

الدوال في JavaScript لا تكتفي باستقبال مدخلات وإرجاع مخرجات — بل تتذكّر أيضاً النطاق (scope) الذي عُرِّفت فيه. هذه الذاكرة تُسمّى الإغلاق (closure)، وهي الآلية التي تقف خلف الـ callbacks ودوال المصانع (factories) ومعظم الأنماط التي ستقابلها في الصفحة التالية.

الأسئلة الشائعة

ما الفرق بين rest و spread في JavaScript؟

الاثنان يستخدمان نفس الصيغة ...، لكن وظيفتيهما متعاكستان تماماً. معامل rest يجمع عدة قيم في مصفوفة واحدة، وتجده عادةً في قائمة بارامترات الدالة أو في عمليات التفكيك (destructuring). أما spread فهو يوسّع عنصراً قابلاً للتكرار أو كائناً إلى أجزائه، ويظهر عند استدعاء الدوال وداخل المصفوفات والكائنات. القاعدة البسيطة: إذا كانت ... في طرف الاستقبال فهي rest، وإذا كانت في طرف الإرسال فهي spread.

كيف يعمل rest parameter داخل الدالة؟

عندما تكتب شيئاً مثل function sum(...nums)، فإن nums تجمع كل الوسائط التي مُرّرت للدالة داخل مصفوفة حقيقية. يجب أن يكون rest parameter هو آخر بارامتر في القائمة. وعلى عكس كائن arguments القديم، فإن rest parameter مصفوفة فعلية، أي يمكنك استخدام .map و .filter و .reduce عليها مباشرةً دون أي تحويل.

هل معامل spread يعمل نسخة عميقة (deep copy)؟

لا. يقوم spread بنسخ مستوى واحد فقط، أي إنه ينتج نسخة سطحية (shallow copy). فمثلاً { ...user } يعطيك كائناً جديداً بنفس المفاتيح في المستوى الأعلى، لكن الكائنات والمصفوفات المتداخلة بداخله تبقى مراجع مشتركة مع الأصل. إذا أردت نسخة عميقة فاستخدم structuredClone(value)، أو مرّر البيانات عبر JSON إذا كانت بيانات بسيطة.

تعلّم البرمجة مع Coddy

ابدأ الآن