استنساخ الكائنات في JavaScript: كيف لا تفسد الكود

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا التعيين البسيط هو فخ؟

\n

تخيل أنك تكتب تطبيقًا للملاحظات. يقوم المستخدم بإنشاء بطاقة بعنوان ونص، ثم يريد عمل نسخة منها لتعديلها قليلاً. تكتب:

\n
const original = { title: 'خطتي', text: 'شراء حليب' };\nconst copy = original;\ncopy.title = 'خطة جديدة';\nconsole.log(original.title); // 'خطة جديدة' — أوه!
\n

موقف مألوف؟ إذا كان الأمر كذلك، فقد واجهت السمة الرئيسية لـ الكائنات في JavaScript: يتم تمريرها بالمرجع. التعيين copy = original لا ينشئ كائنًا جديدًا، بل ينسخ المرجع إلى نفس موقع الذاكرة. أي تغيير في copy ينعكس على original.

\n

على Stack Overflow، جمع سؤال "How do I correctly clone a JavaScript object?" آلاف المشاهدات وعشرات الإجابات. هذه واحدة من أكثر المشكلات شيوعًا للمبتدئين وحتى المطورين ذوي الخبرة. في هذه المقالة، سنستعرض جميع طرق استنساخ الكائنات في JavaScript، من البسيطة إلى المتقدمة، ونكتشف أي نهج هو الأفضل في كل حالة.

\n\n

النسخ السطحي (Shallow copy): سريع وبسيط

\n

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

\n\n

الطريقة 1: Spread-operator (…)

\n

الطريقة الأكثر حداثة وقابلية للقراءة:

\n
const original = { name: 'آنا', age: 25 };\nconst clone = { ...original };\nclone.age = 30;\nconsole.log(original.age); // 25 — كل شيء على ما يرام!
\n

الإيجابيات: كتابة قصيرة، يعمل مع المصفوفات والكائنات. السلبيات: لا ينسخ الطرق (الدوال) والخصائص الرمزية (Symbols).

\n\n

الطريقة 2: Object.assign()

\n

الطريقة الكلاسيكية لـ نسخ الكائن بدون مراجع على المستوى العلوي:

\n
const source = { a: 1, b: { c: 2 } };\nconst target = Object.assign({}, source);\ntarget.a = 100; // source.a لن يتغير\ntarget.b.c = 999; // source.b.c سيتغير أيضًا — نسخ سطحي!
\n

هذه الطريقة مفيدة بشكل خاص عندما تحتاج إلى دمج عدة كائنات: Object.assign({}, obj1, obj2).

\n\n

الطريقة 3: حلقة for…in

\n

طريقة قديمة، لكنها لا تزال تعمل. مفيدة لفهم الآلية:

\n
function shallowClone(obj) {\n  const clone = {};\n  for (let key in obj) {\n    if (obj.hasOwnProperty(key)) {\n      clone[key] = obj[key];\n    }\n  }\n  return clone;\n}
\n

اليوم نادرًا ما تُستخدم، لكنها توضح بشكل ممتاز كيف يعمل استنساخ الكائنات يدويًا.

\n\n

النسخ العميق (Deep copy): عندما تحتاج إلى كل شيء

\n

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

\n\n

الطريقة 4: JSON.parse + JSON.stringify

\n

أشهر "حل مؤقت":

\n
const original = { \n  name: 'بيتر', \n  address: { city: 'موسكو', street: 'تفيرسكايا' },\n  date: new Date()\n};\nconst clone = JSON.parse(JSON.stringify(original));\nclone.address.city = 'سانت بطرسبرغ';\nconsole.log(original.address.city); // 'موسكو' — مرحى!
\n

لكن هناك تفاصيل: هذه الطريقة تفقد الدوال و undefined و Symbol، كما تحول Date إلى سلسلة نصية. للبيانات البسيطة — جيد، للبيانات المعقدة — لا.

\n\n

الطريقة 5: structuredClone() — المعيار الجديد

\n

منذ عام 2022، ظهرت في JavaScript طريقة مدمجة structuredClone:

\n
const original = { \n  scores: [10, 20, 30], \n  meta: { level: 'easy' },\n  date: new Date()\n};\nconst clone = structuredClone(original);\nclone.meta.level = 'hard';\nconsole.log(original.meta.level); // 'easy'
\n

الإيجابيات: تدعم Date و Map و Set و ArrayBuffer والمراجع التكرارية. السلبيات: لا تدعم الدوال وعناصر DOM و Error. تعمل

المدونات

توصيات الكتب