जैसा कि आप जावास्क्रिप्ट में जानते हैं, वस्तुओं को संदर्भ द्वारा कॉपी किया जाता है। लेकिन कभी-कभी आपको किसी वस्तु की गहरी क्लोनिंग करने की आवश्यकता होती है। कई js पुस्तकालयों इस मामले के लिए deepClone फ़ंक्शन के अपने कार्यान्वयन की पेशकश करते हैं। लेकिन, दुर्भाग्य से, अधिकांश पुस्तकालय कई महत्वपूर्ण बातों को ध्यान में नहीं रखते हैं:
- एरे ऑब्जेक्ट में झूठ हो सकता है और उन्हें ऐरे के रूप में कॉपी करना बेहतर है
- ऑब्जेक्ट में कुंजी के रूप में प्रतीक के साथ फ़ील्ड हो सकते हैं
- ऑब्जेक्ट फ़ील्ड में डिफ़ॉल्ट के अलावा अन्य वर्णक होते हैं
- कार्य वस्तु के क्षेत्रों में हो सकते हैं और उन्हें क्लोन करने की भी आवश्यकता होती है।
- एक ऑब्जेक्ट अंत में Object.prototype से अलग एक प्रोटोटाइप है
जिसने इसे तोड़ा, मैंने स्पॉइलर के नीचे पूरा कोड रखाfunction deepClone(source) { return ({ 'object': cloneObject, 'function': cloneFunction }[typeof source] || clonePrimitive)(source)(); } function cloneObject(source) { return (Array.isArray(source) ? () => source.map(deepClone) : clonePrototype(source, cloneFields(source, simpleFunctor({}))) ); } function cloneFunction(source) { return cloneFields(source, simpleFunctor(function() { return source.apply(this, arguments); })); } function clonePrimitive(source) { return () => source; } function simpleFunctor(value) { return mapper => mapper ? simpleFunctor(mapper(value)) : value; } function makeCloneFieldReducer(source) { return (destinationFunctor, field) => { const descriptor = Object.getOwnPropertyDescriptor(source, field); return destinationFunctor(destination => Object.defineProperty(destination, field, 'value' in descriptor ? { ...descriptor, value: deepClone(descriptor.value) } : descriptor)); }; } function cloneFields(source, destinationFunctor) { return (Object.getOwnPropertyNames(source) .concat(Object.getOwnPropertySymbols(source)) .reduce(makeCloneFieldReducer(source), destinationFunctor) ); } function clonePrototype(source, destinationFunctor) { return destinationFunctor(destination => Object.setPrototypeOf(destination, Object.getPrototypeOf(source))); }
मेरा कार्यान्वयन एक कार्यात्मक शैली में लिखा गया है जो मुझे विश्वसनीयता, स्थिरता और सरलता प्रदान करता है। लेकिन, दुर्भाग्य से, कई लोग अभी भी प्रक्रियात्मकता और छद्म ओओपी के साथ अपनी सोच का पुनर्निर्माण नहीं कर सकते हैं, मैं अपने कार्यान्वयन के प्रत्येक भवन ईंट की व्याख्या करूंगा:
DeepClone फ़ंक्शन स्वयं 1 तर्क स्रोत लेगा - वह स्रोत जिससे हम क्लोन करेंगे, और उपरोक्त सभी विशेषताओं के साथ इसका गहरा क्लोन वापस आ जाएगा:
function deepClone(source) { return ({ 'object': cloneObject, 'function': cloneFunction }[typeof source] || clonePrimitive)(source)(); }
यहां सब कुछ सरल है, स्रोत में डेटा के प्रकार के आधार पर, एक फ़ंक्शन का चयन किया जाता है जो इसे क्लोन कर सकता है, और स्रोत स्वयं इसे स्थानांतरित कर देता है।
आप यह भी देख सकते हैं कि उपयोगकर्ता को वापस किए जाने से पहले दिए गए परिणाम को मापदंडों के बिना एक फ़ंक्शन के रूप में कहा जाता है। यह आवश्यक है, चूंकि मैं उस मूल्य को लपेटता हूं जिसमें मैं एक साधारण फ़नकार में, सहायक कार्यों की शुद्धता का उल्लंघन किए बिना इसे म्यूट करने में सक्षम होने के लिए। यहाँ इस फ़नकार का कार्यान्वयन है:
function simpleFunctor(value) { return mapper => mapper ? simpleFunctor(mapper(value)) : value; }
वह 2 चीजें कर सकता है - नक्शा (यदि मैपर फ़ंक्शन उसे पास किया जाता है) और निकालें (यदि कुछ भी पारित नहीं हुआ है)।
अब हम असिस्टेंट फ़ंक्शंस का विश्लेषण करेंगे क्लोनऑब्जेक्ट, क्लोनफ़ंक्शन और क्लोनप्रिमेटिक। उनमें से प्रत्येक एक विशिष्ट प्रकार के स्रोत का 1 तर्क लेता है और उसका क्लोन लौटाता है।
क्लोनबॉजेक्ट के कार्यान्वयन को ध्यान में रखना चाहिए कि सरणियाँ भी प्रकार की वस्तु हैं, ठीक है, अन्य मामलों में, उन्हें फ़ील्ड और प्रोटोटाइप को क्लोन करना होगा। यहाँ इसका कार्यान्वयन है:
function cloneObject(source) { return (Array.isArray(source) ? () => source.map(deepClone) : clonePrototype(source, cloneFields(source, simpleFunctor({}))) ); }
सरणी को स्लाइस विधि का उपयोग करके कॉपी किया जा सकता है, लेकिन चूंकि हमारे पास गहरी क्लोनिंग है और सरणी में न केवल आदिम मान हो सकते हैं, मैप विधि का उपयोग तर्क के रूप में ऊपर वर्णित डीप क्लोन के साथ किया जाता है।
अन्य वस्तुओं के लिए, हम एक नई वस्तु बनाते हैं और इसे ऊपर वर्णित हमारे फ़नकार में लपेटते हैं, क्लोन (फ़ील्ड के साथ-साथ (डिस्क्रिप्टर के साथ)) क्लोनफिल्ड हेल्पर फ़ंक्शन का उपयोग करते हुए, और फिर क्लोनप्रोटोटाइप का उपयोग करके प्रोटोटाइप को क्लोन करते हैं।
हेल्पर फ़ंक्शन मैं नीचे वर्णन करेगा। इस बीच,
क्लोनफंक्शन के कार्यान्वयन पर विचार करें:
function cloneFunction(source) { return cloneFields(source, simpleFunctor(function() { return source.apply(this, arguments); })); }
आप बस सभी तर्कों के साथ एक फ़ंक्शन का क्लोन नहीं कर सकते। लेकिन आप इसे किसी अन्य फ़ंक्शन में लपेट सकते हैं जो मूल को सभी तर्कों और संदर्भों के साथ कहता है, और इसका परिणाम देता है। ऐसा "क्लोन" निश्चित रूप से मूल फ़ंक्शन को स्मृति में रखेगा, लेकिन यह मूल तर्क को थोड़ा और पूरी तरह से "तौलना" करेगा। हम एक फंक्सन में क्लोन किए गए फ़ंक्शन को लपेटते हैं और क्लोनफिल्ड्स का उपयोग करके हम मूल फ़ंक्शन से सभी फ़ील्ड्स को इसमें कॉपी करते हैं, क्योंकि JS में फ़ंक्शन भी एक ऑब्जेक्ट है, बस कहा जाता है, और इसलिए इसमें फ़ील्ड स्टोर कर सकते हैं।
संभावित रूप से, एक फ़ंक्शन फंक्शन.प्रोटोटाइप से अलग एक प्रोटोटाइप हो सकता है, लेकिन मैंने इस चरम मामले पर विचार नहीं किया। एफपी के आकर्षण में से एक यह है कि हम आवश्यक कार्यक्षमता को लागू करने के लिए एक मौजूदा फ़ंक्शन पर आसानी से एक नया आवरण जोड़ सकते हैं।
अंतिम क्लोनप्रिमेटिक बिल्डिंग ईंट आदिम मूल्यों को क्लोन करने का कार्य करती है। लेकिन चूँकि आदिम मानों को मूल्य (या संदर्भ द्वारा, लेकिन JS इंजनों के कुछ कार्यान्वयन में अपरिवर्तनीय) से कॉपी किया जाता है, इसलिए हम उन्हें कॉपी कर सकते हैं। लेकिन चूँकि हमें शुद्ध मूल्य मिलने की उम्मीद नहीं है, लेकिन फ़न में लिपटे एक मूल्य, जिसे अर्क तर्कों के बिना कह सकते हैं, हम एक फ़ंक्शन में हमारे मूल्य को लपेटेंगे:
function clonePrimitive(source) { return () => source; }
अब हम उन सहायक कार्यों को लागू करते हैं जो ऊपर उपयोग किए गए थे - क्लोनप्रोटोटाइप और क्लोनफोन
एक प्रोटोटाइप को क्लोन करने के लिए,
क्लोनप्रोटोटाइप केवल ऑब्जेक्ट ऑब्जेक्ट से प्रोटोटाइप को निकाल देगा और,
परिणामी फ़ंक्टर पर मैप ऑपरेशन करके, इसे लक्ष्य ऑब्जेक्ट पर सेट करेगा:
function clonePrototype(source, destinationFunctor) { return destinationFunctor(destination => Object.setPrototypeOf(destination, Object.getPrototypeOf(source))); }
क्लोनिंग फ़ील्ड थोड़ा अधिक जटिल है, इसलिए मैं
क्लोनफिल्ड फ़ंक्शन को दो में विभाजित करता
हूं । बाहरी फ़ंक्शन सभी नामित फ़ील्ड और सभी प्रतीक फ़ील्ड का संयोजन लेता है, बिल्कुल सभी फ़ील्ड प्राप्त करता है, और उन्हें सहायक फ़ंक्शन द्वारा बनाए गए रिड्यूसर के माध्यम से चलाता है:
function cloneFields(source, destinationFunctor) { return (Object.getOwnPropertyNames(source) .concat(Object.getOwnPropertySymbols(source)) .reduce(makeCloneFieldReducer(source), destinationFunctor) ); }
makeCloneFieldReducer को हमारे लिए एक reducer फ़ंक्शन बनाना चाहिए जो स्रोत ऑब्जेक्ट के सभी क्षेत्रों की एक सरणी पर कम विधि को पारित किया जा सकता है। एक बैटरी के रूप में, लक्ष्य को संग्रहीत करने वाले हमारे फ़नकार का उपयोग किया जाएगा। Reducer को स्रोत ऑब्जेक्ट के क्षेत्र से हैंडल निकालना होगा और इसे लक्ष्य ऑब्जेक्ट के क्षेत्र में असाइन करना होगा। लेकिन यहां यह विचार करना महत्वपूर्ण है कि दो प्रकार के वर्णनकर्ता हैं - मूल्य के साथ और प्राप्त / सेट के साथ। जाहिर है, मूल्य को क्लोन करने की आवश्यकता है, लेकिन प्राप्त / सेट के साथ ऐसी कोई आवश्यकता नहीं है, इस तरह के एक विवरणक को लौटाया जा सकता है:
function makeCloneFieldReducer(source) { return (destinationFunctor, field) => { const descriptor = Object.getOwnPropertyDescriptor(source, field); return destinationFunctor(destination => Object.defineProperty(destination, field, 'value' in descriptor ? { ...descriptor, value: deepClone(descriptor.value) } : descriptor)); }; }
वह सब है। डीप क्लोन का ऐसा कार्यान्वयन लेख की शुरुआत में उत्पन्न सभी समस्याओं को हल करता है। इसके अलावा, यह शुद्ध कार्यों और एक फ़नकार पर बनाया गया है, जो लैम्ब्डा कैलकुलस में निहित सभी गारंटी देता है।
मैं यह भी ध्यान देता हूं कि मैंने एक सरणी के अलावा संग्रह के लिए उत्कृष्ट व्यवहार को लागू नहीं किया है जो कि व्यक्तिगत रूप से क्लोन करने के लायक होगा, जैसे कि मैप या सेट। हालांकि कुछ मामलों में यह आवश्यक हो सकता है।