مقدمة: لماذا التعيين البسيط هو فخ؟
\nتخيل أنك تكتب تطبيقًا للملاحظات. يقوم المستخدم بإنشاء بطاقة بعنوان ونص، ثم يريد عمل نسخة منها لتعديلها قليلاً. تكتب:
\nconst original = { title: 'خطتي', text: 'شراء حليب' };\nconst copy = original;\ncopy.title = 'خطة جديدة';\nconsole.log(original.title); // 'خطة جديدة' — أوه!\nموقف مألوف؟ إذا كان الأمر كذلك، فقد واجهت السمة الرئيسية لـ الكائنات في JavaScript: يتم تمريرها بالمرجع. التعيين copy = original لا ينشئ كائنًا جديدًا، بل ينسخ المرجع إلى نفس موقع الذاكرة. أي تغيير في copy ينعكس على original.
على Stack Overflow، جمع سؤال "How do I correctly clone a JavaScript object?" آلاف المشاهدات وعشرات الإجابات. هذه واحدة من أكثر المشكلات شيوعًا للمبتدئين وحتى المطورين ذوي الخبرة. في هذه المقالة، سنستعرض جميع طرق استنساخ الكائنات في JavaScript، من البسيطة إلى المتقدمة، ونكتشف أي نهج هو الأفضل في كل حالة.
\n\nالنسخ السطحي (Shallow copy): سريع وبسيط
\nالنسخ السطحي ينشئ كائنًا جديدًا، لكن بالنسبة للكائنات المتداخلة (المصفوفات، الكائنات الأخرى) ينسخ المراجع فقط. هذا مثالي للهياكل المسطحة، لكنه خطير للهياكل العميقة.
\n\nالطريقة 1: Spread-operator (…)
\nالطريقة الأكثر حداثة وقابلية للقراءة:
\nconst original = { name: 'آنا', age: 25 };\nconst clone = { ...original };\nclone.age = 30;\nconsole.log(original.age); // 25 — كل شيء على ما يرام!\nالإيجابيات: كتابة قصيرة، يعمل مع المصفوفات والكائنات. السلبيات: لا ينسخ الطرق (الدوال) والخصائص الرمزية (Symbols).
\n\nالطريقة 2: Object.assign()
\nالطريقة الكلاسيكية لـ نسخ الكائن بدون مراجع على المستوى العلوي:
\nconst 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).
الطريقة 3: حلقة for…in
\nطريقة قديمة، لكنها لا تزال تعمل. مفيدة لفهم الآلية:
\nfunction 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أشهر "حل مؤقت":
\nconst 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 إلى سلسلة نصية. للبيانات البسيطة — جيد، للبيانات المعقدة — لا.
الطريقة 5: structuredClone() — المعيار الجديد
\nمنذ عام 2022، ظهرت في JavaScript طريقة مدمجة structuredClone:
\nconst 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. تعمل