स्ट्रक्चर्डClone का इस्तेमाल करके, JavaScript में डीप-कॉपी करना

प्लैटफ़ॉर्म में अब structuredClone() फ़ंक्शन शामिल है. यह डीप-कॉपी करने के लिए, पहले से मौजूद फ़ंक्शन है.

लंबे समय तक, JavaScript वैल्यू की डीप कॉपी बनाने के लिए, आपको अलग-अलग तरीके और लाइब्रेरी का इस्तेमाल करना पड़ता था. प्लैटफ़ॉर्म में अब structuredClone() शामिल है. यह डीप कॉपी करने के लिए, पहले से मौजूद फ़ंक्शन है.

ब्राउज़र के इस्तेमाल से जुड़ी सहायता

  • Chrome: 98.
  • Edge: 98.
  • Firefox: 94.
  • Safari: 15.4.

सोर्स

हल्की कॉपी

JavaScript में किसी वैल्यू को कॉपी करने की प्रोसेस, डीप के बजाय शैलो होती है. इसका मतलब है कि नेस्ट की गई वैल्यू में किए गए बदलाव, कॉपी के साथ-साथ ओरिजनल में भी दिखेंगे.

ऑब्जेक्ट स्प्रेड ऑपरेटर ... का इस्तेमाल करके, JavaScript में शैलो कॉपी बनाने का एक तरीका:

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

शैलो कॉपी में सीधे तौर पर कोई प्रॉपर्टी जोड़ने या उसमें बदलाव करने से, सिर्फ़ कॉपी पर असर पड़ेगा, ओरिजनल पर नहीं:

myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`

हालांकि, नेस्ट की गई प्रॉपर्टी को जोड़ने या उसमें बदलाव करने से, कॉपी और ओरिजनल, दोनों पर असर पड़ता है:

myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp) 
// ^ logs `a new value`

स्प्रेड ऑपरेटर का इस्तेमाल करके, {...myOriginal} एक्सप्रेशन myOriginal की (गिनती की जा सकने वाली) प्रॉपर्टी पर फिर से लागू होता है. यह प्रॉपर्टी के नाम और वैल्यू का इस्तेमाल करता है. साथ ही, उन्हें एक-एक करके नए बनाए गए खाली ऑब्जेक्ट को असाइन करता है. इसलिए, नतीजे में मिलने वाला ऑब्जेक्ट आकार में एक जैसा होता है, लेकिन प्रॉपर्टी और वैल्यू की सूची की अपनी कॉपी होती है. वैल्यू भी कॉपी कर ली जाती हैं, लेकिन तथाकथित प्रिमिटिव वैल्यू को JavaScript वैल्यू, नॉन-प्रीमिटिव वैल्यू से अलग तरीके से मैनेज करती है. MDN को कोट करने के लिए:

JavaScript में, प्राइमटिव (प्राइमटिव वैल्यू, प्राइमटिव डेटा टाइप) ऐसा डेटा होता है जो ऑब्जेक्ट नहीं होता और जिसमें कोई तरीका नहीं होता. प्राइमटिव डेटा टाइप सात तरह के होते हैं: स्ट्रिंग, संख्या, बिगेंट, बूलियन, अनडिफ़ाइन्ड, सिंबल, और शून्य.

एमडीएन — प्राइमिटिव

प्राइमटिव वैल्यू के बजाय, अन्य वैल्यू को रेफ़रंस के तौर पर मैनेज किया जाता है. इसका मतलब है कि वैल्यू को कॉपी करने का मतलब, उसी ऑब्जेक्ट के रेफ़रंस को कॉपी करना है. इस वजह से, शैलो कॉपी का व्यवहार होता है.

डीप कॉपी

शैलो कॉपी के उलट डीप कॉपी होती है. डीप कॉपी एल्गोरिदम, किसी ऑब्जेक्ट की प्रॉपर्टी को भी एक-एक करके कॉपी करता है. हालांकि, जब उसे किसी दूसरे ऑब्जेक्ट का रेफ़रंस मिलता है, तो वह अपने-आप बार-बार लागू होता है और उस ऑब्जेक्ट की भी कॉपी बनाता है. यह पक्का करने के लिए यह बहुत ज़रूरी हो सकता है कि दो कोड के हिस्से, गलती से कोई ऑब्जेक्ट शेयर न करें और अनजाने में एक-दूसरे की स्थिति में बदलाव न करें.

JavaScript में किसी वैल्यू की डीप-कॉपी बनाने का कोई आसान या अच्छा तरीका नहीं था. कई लोग तीसरे पक्ष की लाइब्रेरी का इस्तेमाल करते थे, जैसे कि Lodash का cloneDeep() फ़ंक्शन. इस समस्या को हल करने का सबसे आम तरीका, JSON-आधारित हैक था:

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));

असल में, यह समस्या हल करने का इतना लोकप्रिय तरीका था कि JSON.parse() को V8 ने ज़्यादा से ज़्यादा ऑप्टिमाइज़ किया. खास तौर पर, ऊपर दिए गए पैटर्न को ज़्यादा से ज़्यादा तेज़ बनाने के लिए. यह तरीका तेज़ है, लेकिन इसमें कुछ कमियां और समस्याएं हैं:

  • रिकर्सिव डेटा स्ट्रक्चर: JSON.stringify(), रिकर्सिव डेटा स्ट्रक्चर देने पर गड़बड़ी का मैसेज दिखाएगा. लिंक की गई सूचियों या ट्री का इस्तेमाल करते समय, ऐसा आसानी से हो सकता है.
  • पहले से मौजूद टाइप: अगर वैल्यू में Map, Set, Date, RegExp या ArrayBuffer जैसे दूसरे JS पहले से मौजूद टाइप शामिल हैं, तो JSON.stringify() गड़बड़ी का मैसेज दिखाएगा.
  • फ़ंक्शन: JSON.stringify() अपने-आप फ़ंक्शन को खारिज कर देगा.

स्ट्रक्चर्ड क्लोनिंग

प्लैटफ़ॉर्म को पहले से ही कुछ जगहों पर JavaScript वैल्यू की डीप कॉपी बनाने की ज़रूरत थी: IndexedDB में JS वैल्यू को स्टोर करने के लिए, किसी तरह का सीरियलाइज़ेशन ज़रूरी है, ताकि उसे डिस्क पर स्टोर किया जा सके और बाद में JS वैल्यू को वापस लाने के लिए, उसे डीसीरियलाइज़ किया जा सके. इसी तरह, postMessage() के ज़रिए किसी वेबवर्क को मैसेज भेजने के लिए, JS वैल्यू को एक JS रीलम से दूसरे रीलम में ट्रांसफ़र करना ज़रूरी है. इसके लिए इस्तेमाल किए जाने वाले एल्गोरिदम को “स्ट्रक्चर्ड क्लोन” कहा जाता है. हाल ही तक, डेवलपर के लिए इसे ऐक्सेस करना आसान नहीं था.

अब ऐसा नहीं है! एचटीएमएल स्पेसिफ़िकेशन में बदलाव किया गया था, ताकि structuredClone() नाम का फ़ंक्शन जोड़ा जा सके. यह फ़ंक्शन, डेवलपर के लिए JavaScript वैल्यू की डीप कॉपी बनाने का एक आसान तरीका है.

const myDeepCopy = structuredClone(myOriginal);

बस इतना ही! यह पूरा एपीआई है. ज़्यादा जानकारी के लिए, MDN का लेख पढ़ें.

सुविधाएं और सीमाएं

स्ट्रक्चर्ड क्लोनिंग, JSON.stringify() तकनीक की कई कमियों को ठीक करती है. हालांकि, सभी कमियों को ठीक नहीं करती. स्ट्रक्चर्ड क्लोनिंग, साइक्लिकल डेटा स्ट्रक्चर को मैनेज कर सकती है और कई तरह के डेटा के साथ काम कर सकती है. आम तौर पर, यह ज़्यादा मज़बूत और तेज़ होता है.

हालांकि, इसमें अब भी कुछ सीमाएं हैं, जिनकी वजह से आपको परेशानी हो सकती है:

  • प्रोटोटाइप: अगर किसी क्लास इंस्टेंस के साथ structuredClone() का इस्तेमाल किया जाता है, तो आपको रिटर्न वैल्यू के तौर पर एक सादा ऑब्जेक्ट मिलेगा. इसकी वजह यह है कि स्ट्रक्चर्ड क्लोनिंग, ऑब्जेक्ट की प्रोटोटाइप चेन को खारिज कर देती है.
  • फ़ंक्शन: अगर आपके ऑब्जेक्ट में फ़ंक्शन हैं, तो structuredClone() एक DataCloneError अपवाद दिखाएगा.
  • क्लोन नहीं की जा सकने वाली वैल्यू: कुछ वैल्यू को क्लोन नहीं किया जा सकता. इनमें Error और DOM नोड सबसे अहम हैं. इससे structuredClone() को थ्रो किया जाएगा.

अगर इनमें से कोई भी सीमा आपके इस्तेमाल के उदाहरण के लिए सही नहीं है, तो Lodash जैसी लाइब्रेरी अब भी डीप-क्लोनिंग के अन्य एल्गोरिदम को कस्टम तरीके से लागू करती हैं. ये एल्गोरिदम आपके इस्तेमाल के उदाहरण के हिसाब से सही हो सकते हैं या नहीं.

परफ़ॉर्मेंस

मैंने नया माइक्रो-बेंचमार्क नहीं किया है. हालांकि, structuredClone() के एक्सपोज़ होने से पहले, मैंने 2018 की शुरुआत में तुलना की थी. उस समय, JSON.parse() बहुत छोटे ऑब्जेक्ट के लिए सबसे तेज़ विकल्प था. मुझे उम्मीद है कि यह सुविधा पहले की तरह ही काम करती रहेगी. स्ट्रक्चर्ड क्लोनिंग पर आधारित तकनीकें, बड़े ऑब्जेक्ट के लिए काफ़ी तेज़ थीं. हमारा सुझाव है कि डीप कॉपी बनाने के लिए, structuredClone() का इस्तेमाल डिफ़ॉल्ट तौर पर करें. ऐसा इसलिए, क्योंकि नया structuredClone(), अन्य एपीआई का गलत इस्तेमाल किए बिना काम करता है और JSON.parse() की तुलना में ज़्यादा बेहतर है.

नतीजा

अगर आपको JS में किसी वैल्यू की डीप-कॉपी बनानी है, तो अब आपको समस्या हल करने के तरीके या लाइब्रेरी का इस्तेमाल करने की ज़रूरत नहीं है. ऐसा तब किया जा सकता है, जब आपने डेटा स्ट्रक्चर में बदलाव न करने की सुविधा का इस्तेमाल किया हो या आपको यह पक्का करना हो कि कोई फ़ंक्शन ओरिजनल पर असर डाले बिना किसी ऑब्जेक्ट में बदलाव कर सकता है. JS नेटवर्क में अब structuredClone() है. वाह.