structuredClone بدل حيلة JSON: نسخة عميقة صح في JavaScript
ليه JSON.parse(JSON.stringify()) بيبوّظ Date و Map والـ circular references، وإزاي structuredClone() بيعمل deep copy صح، وإيه اللي مش بينسخه.
لو لسه بتعمل نسخة عميقة (deep copy) من object بـ JSON.parse(JSON.stringify(obj))، فالحيلة دي شغالة لحد ما الداتا يبقى فيها Date أو Map أو reference دايري، وساعتها بتبوّظ من غير ما تحس. الحل موجود built-in من سنين: structuredClone().
المشكلة في حيلة الـ JSON
الـ JSON مش مصمم يشيل كل أنواع JavaScript، فلما بتعدّي الداتا من خلاله بيحصل الآتي:
- Date بيرجع string، مش
Dateتاني. - Map و Set بيرجعوا
{}فاضيين. - القيم اللي بـ
undefinedوالـ functions بتختفي من الـ object. - لو فيه circular reference بيرمي
TypeErrorعلى طول.
const order = { createdAt: new Date(), items: new Map([["sku-1", 2]]) };
const bad = JSON.parse(JSON.stringify(order));
typeof bad.createdAt; // "string"
bad.items; // {}
الحل: structuredClone
structuredClone(value) بيعمل deep copy باستخدام الـ structured clone algorithm، نفس الخوارزمية اللي المتصفح بيستخدمها في postMessage() مع الـ Workers وفي IndexedDB.
const copy = structuredClone(order);
copy.createdAt instanceof Date; // true
copy.items.get("sku-1"); // 2
حسب MDN هو Baseline Widely available في المتصفحات من مارس 2022، وفي Node.js موجود كـ global من v17.0.0. يعني مش محتاج lodash عشان النقطة دي.
بيدعم إيه؟
- الأنواع الأساسية (ماعدا
symbol)، وArrayو plain objects. DateوMapوSetوRegExpوArrayBufferو TypedArrays.- أنواع الـ
Errorالأساسية، وكمانBlobوFileوImageDataوغيرهم من الـ Web APIs. - Circular references: الـ reference الدايري بيتنسخ صح.
const a = { name: "MDN" };
a.itself = a;
const clone = structuredClone(a);
clone.itself === clone; // true
الحاجات اللي مش بينسخها
- Functions بترمي
DataCloneError. - DOM nodes بترمي
DataCloneErrorبرضه. - الـ prototype chain مش بيتنسخ، فالـ class instance بيرجع plain object من غير methods.
- الـ getters/setters والـ property descriptors مش بتتنسخ، و
lastIndexبتاعRegExpمش بيتحفظ. - الـ class private fields مش بتتنسخ.
Bonus: الـ transfer
تقدر تنقل (transfer) ملكية ArrayBuffer بدل ما تنسخه، ودي مفيدة مع الداتا الكبيرة. بعد النقل الـ buffer الأصلي بيبقى فاضي:
const u8 = new Uint8Array(1024);
const moved = structuredClone(u8, { transfer: [u8.buffer] });
u8.byteLength; // 0
رأيي
structuredClone هو الاختيار الافتراضي لأي deep copy لداتا عادية: state، config، أو response جاي من API. بس لو الـ object بتاعك class instance ومحتاج الـ methods، أو فيه functions، يبقى محتاج copy constructor أو factory بإيدك. وما تستخدمش حيلة الـ JSON غير لو فعلًا عايز تحوّل لـ JSON.
المصادر
- javascript
- structuredclone
- deep-copy
- nodejs
- web-apis