Menu
العربية

معامل النشر للكائنات في JavaScript: نسخ ودمج

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

معامل النشر في جافا سكريبت: توسيع كائن داخل كائن آخر

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

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

copy يحتوي على نفس المفاتيح والقيم الموجودة في user، لكنه كائن مختلف تمامًا. تعديل أحدهما لن يؤثر على الآخر — على الأقل في المستوى الأول. سنعود لهذه النقطة المهمة بعد قليل.

الفكرة ببساطة: عندما تكتب { ...obj } فأنت تقول "انسخ خصائص obj داخل هذا الكائن الجديد". وأي شيء تضيفه بجانب المعامل ... يصبح جزءًا من النتيجة.

نسخ كائن في جافا سكريبت مع تعديل بعض الخصائص دفعة واحدة

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

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

الكائن user يبقى كما هو دون أي تعديل، بينما updated هو كائن جديد تم فيه استبدال قيمة role. هذا النمط في التحديث غير القابل للتغيير (immutable update) ستصادفه في كل مكان في جافا سكريبت الحديثة — سواء في تحديثات الحالة في React، أو في reducers الخاصة بـ Redux، أو في أي كود يتجنب التعديل المباشر على الكائنات.

اعكس الترتيب وستحصل على نتيجة معاكسة تماماً:

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

هنا جاء role: "guest" أولًا، لذلك فإن user.role سيطغى عليه. هذا مفيد عندما تريد وضع قيم افتراضية يمكن للكائن المنشور استبدالها.

دمج الكائنات في جافا سكريبت

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

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

theme و fontSize مصدرهما userPrefs، أما debug فينزل من defaults. سواء كان لديك ثلاثة كائنات أو أربعة، القاعدة ثابتة: القراءة تتم بالترتيب، والقيمة الأخيرة هي التي تفوز.

هذه هي الطريقة الحديثة التي حلّت محل Object.assign({}, defaults, userPrefs). النتيجة واحدة، لكن صيغة الـ spread أوضح في القراءة، وتُجنّبك خطأً شائعاً يقع فيه الكثيرون وهو كتابة Object.assign(defaults, userPrefs) — الذي يُعدّل defaults مباشرةً.

معامل النشر في جافا سكريبت ينسخ نسخة سطحية فقط

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

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

تغيير copy.address.city أدّى إلى تغيير user.address.city أيضًا — والسبب أن الكائنين يتشاركان نفس كائن address. فمعامل النشر لم يُنشئ لنا سوى غلاف خارجي جديد فقط.

لذلك عندما تحتاج إلى تعديل قيمة متداخلة، طبّق معامل النشر على كل مستوى تريد تغييره:

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

للحصول على نسخة عميقة حقيقية لأي بيانات، استخدم structuredClone(obj). هذه الدالة تتعامل مع الكائنات المتشعبة والمصفوفات والتواريخ وكائنات Map وSet — وهي مدمجة في كل بيئات التشغيل الحديثة.

الفرق بين spread و rest

الاثنان يستخدمان النقاط الثلاث نفسها، لكن وظيفتهما معاكسة تمامًا. معامل spread يوزّع العناصر، بينما rest يجمّعها.

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

قاعدة بسيطة تسهّل التفريق: لو لاقيت ... قبل علامة = (أي داخل عملية destructuring)، فهذا rest. أما لو كانت داخل كائن أو مصفوفة بعد علامة =، فهذا spread.

حذف خاصية من كائن بدون تعديله

لما تدمج rest في الـ destructuring مع spread، تحصل على طريقة أنيقة لإنشاء نسخة من الكائن مع استبعاد مفتاح معيّن — بدون delete وبدون تعديل الكائن الأصلي:

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

يُستخرج tempToken إلى متغير مستقل (تتجاهله أنت)، وكل ما تبقى يذهب إلى safe. أما الكائن الأصلي user فيبقى كما هو دون أي تغيير.

ما الذي لا ينسخه معامل النشر؟

ثمة تفاصيل دقيقة يجدر بك معرفتها:

  • الخصائص غير القابلة للتعداد (Non-enumerable) لا تُنسخ. معظم الخصائص التي تُنشئها تكون قابلة للتعداد افتراضيًا، لكن الخصائص المُعرَّفة عبر Object.defineProperty وبعض الخصائص المدمجة في اللغة ليست كذلك.
  • البروتوتايب (prototype) لا يُنسخ. فعند كتابة { ...instance } ستحصل على كائن عادي (plain object)، لا على نسخة من الكلاس الأصلي. وبالتالي فإن التوابع المُعرَّفة على prototype الكلاس لن تظهر في النسخة الجديدة.
  • الـ getters يتم تنفيذها. عند نشر كائن يحتوي على getter، يُستدعى هذا الـ getter مرة واحدة، ثم تُخزَّن القيمة الناتجة كخاصية عادية في الكائن الجديد.
index.js
Output
Click Run to see the output here.

الكائن copy يحتوي على x و y، لكنه صار كائنًا عاديًا — فالدالة distance معرّفة على Point.prototype، ومعامل النشر لا يصل إليها. إذا أردت استنساخ نسخة من صنف (class instance)، فالأفضل أن يوفّر الصنف نفسه دالة clone خاصة به.

التالي: دوال المصفوفات

معامل النشر جزء واحد فقط من أدوات التعامل مع البيانات غير القابلة للتعديل (immutable data). والجزء الآخر المهم هو دوال المصفوفات — مثل map و filter و reduce ورفاقها — وهي دوال تُرجِع مصفوفات جديدة بدلًا من تعديل الأصلية. هذا ما سنراه في الصفحة التالية.

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

ماذا يفعل ...obj في جافا سكريبت؟

داخل كائن حرفي، يقوم ...obj بنسخ كل الخصائص الخاصة القابلة للتعداد من الكائن الأصلي إلى الكائن الجديد. يعني { ...user } ينتج لك كائنًا جديدًا بنفس المفاتيح والقيم الموجودة في user. وهي الطريقة القياسية لنسخ الكائنات أو دمجها دون المساس بالأصل.

كيف أدمج كائنين في جافا سكريبت؟

ببساطة انشرهما معًا داخل كائن جديد هكذا: const merged = { ...a, ...b }. الخصائص القادمة من b ستطغى على أي مفتاح مكرر في a، لأن الأخير هو من يفوز دائمًا. النتيجة نفسها التي تحصل عليها من Object.assign({}, a, b)، لكن الصيغة هنا أوضح وأنظف.

هل معامل النشر يُنتج نسخة عميقة (deep copy)؟

لا. معامل النشر يُنفّذ نسخًا سطحيًا (shallow copy) فقط — أي أنه ينسخ الخصائص في المستوى الأول، أما الكائنات والمصفوفات المتداخلة فتبقى مشتركة بالمرجع. لو عدّلت copy.address.city فسيتغيّر معها original.address.city أيضًا. للحصول على نسخة عميقة حقيقية استخدم structuredClone(obj).

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

الاثنان يُكتبان بنفس الصيغة (...) لكنهما يقومان بعملَين متعاكسَين. النشر (spread) يُفكّك الكائن أو المصفوفة إلى خصائص أو عناصر مفردة، مثل { ...user }. أما الباقي (rest) فيُجمّع ما تبقّى داخل متغير واحد، مثل const { name, ...others } = user. السياق هو ما يُحدّد أيهما أمامك.

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

ابدأ الآن