
जावास्क्रिप्ट ने मुझे हमेशा आश्चर्यचकित किया, सबसे पहले, क्योंकि यह शायद किसी अन्य व्यापक भाषा की तरह एक ही समय में दोनों प्रतिमानों का समर्थन नहीं करता है: सामान्य और असामान्य प्रोग्रामिंग। और अगर लगभग सब कुछ पर्याप्त सर्वोत्तम प्रथाओं और टेम्पलेट्स के बारे में पढ़ा गया है, तो आप कैसे कोड नहीं लिख सकते हैं, लेकिन आप कर सकते हैं की अद्भुत दुनिया केवल थोड़ी सी अजर बनी हुई है।
इस लेख में, हम एक और आकस्मिक कार्य का विश्लेषण करेंगे जिसमें एक सामान्य समाधान के अक्षम्य दुरुपयोग की आवश्यकता होती है।
पिछला कार्य:
एक डेकोरेटर फ़ंक्शन को लागू करें जो पारित फ़ंक्शन को कॉल की संख्या गिनता है और मांग पर इस नंबर को प्राप्त करने की क्षमता प्रदान करता है। समाधान घुंघराले कोष्ठक और वैश्विक चर का उपयोग नहीं करता है।
कॉल काउंटर सिर्फ एक बहाना है, क्योंकि वहाँ कंसोल है ।.count () । लब्बोलुआब यह है कि हमारा फ़ंक्शन लिपटे फ़ंक्शन को कॉल करते समय कुछ डेटा जमा करता है और उन्हें एक्सेस करने के लिए एक निश्चित इंटरफ़ेस प्रदान करता है। यह एक कॉल के सभी परिणामों को सहेज सकता है, और लॉग एकत्र कर सकता है, और किसी प्रकार का संस्मरण। बस एक काउंटर - सभी के लिए आदिम और समझने योग्य।
सभी जटिलता एक असामान्य प्रतिबंध में है। आप घुंघराले ब्रेसिज़ का उपयोग नहीं कर सकते हैं, जिसका अर्थ है कि आपको हर रोज़ अभ्यास और साधारण वाक्यविन्यास पर पुनर्विचार करना होगा।
आदतन हल
पहले आपको एक प्रारंभिक बिंदु चुनने की आवश्यकता है। आमतौर पर, यदि भाषा या इसका विस्तार आवश्यक सजावट फ़ंक्शन प्रदान नहीं करता है, तो हम अपने दम पर कुछ कंटेनर को लागू करेंगे: एक लिपटे फ़ंक्शन, संचित डेटा, और उन्हें एक्सेस करने के लिए एक इंटरफ़ेस। यह अक्सर एक वर्ग है:
class CountFunction { constructor(f) { this.calls = 0; this.f = f; } invoke() { this.calls += 1; return this.f(...arguments); } } const csum = new CountFunction((x, y) => x + y); csum.invoke(3, 7);
यह हमें अभी से शोभा नहीं देता, क्योंकि:
- जावास्क्रिप्ट में, आप इस तरह से एक निजी संपत्ति को लागू नहीं कर सकते हैं: हम दोनों उदाहरण कॉल (जिसे हमें ज़रूरत है) पढ़ सकते हैं और बाहर से मूल्य लिख सकते हैं (जिसकी हमें ज़रूरत नहीं है)। बेशक, हम कंस्ट्रक्टर में क्लोजर का उपयोग कर सकते हैं, लेकिन फिर कक्षा का अर्थ क्या है? और मैं अभी भी बेबल 7 के बिना नए निजी क्षेत्रों का उपयोग करने से डरूंगा।
- भाषा एक कार्यात्मक प्रतिमान का समर्थन करती है, और नए उदाहरण के माध्यम से एक उदाहरण बनाना यहां सबसे अच्छा समाधान नहीं है। यह एक फ़ंक्शन लिखने के लिए अच्छा है जो किसी अन्य फ़ंक्शन को लौटाता है। हाँ!
- अंत में, ClassDeclaration और MethodDefinition का सिंटैक्स हमें सभी घुंघराले ब्रेसिज़ से छुटकारा पाने की अनुमति नहीं देगा।
लेकिन हमारे पास एक अद्भुत मॉड्यूल पैटर्न है जो क्लोजर का उपयोग करके गोपनीयता को लागू करता है:
function count(f) { let calls = 0; return { invoke: function() { calls += 1; return f(...arguments); }, getCalls: function() { return calls; } }; } const csum = count((x, y) => x + y); csum.invoke(3, 7);
आप पहले से ही इसके साथ काम कर सकते हैं।
मनोरंजक निर्णय
यहां ब्रेसिज़ का उपयोग क्यों किया जाता है? ये 4 अलग-अलग मामले हैं:
- एक गिनती समारोह के शरीर को परिभाषित करना
- लौटी हुई वस्तु का आरंभ
- दो समारोह के साथ आह्वान फ़ंक्शन बॉडी ( फंक्शनएक्सप्रेशन ) की परिभाषा
- एक एकल अभिव्यक्ति के साथ getCalls ( FunctionExpression ) फ़ंक्शन के शरीर को परिभाषित करना
दूसरे पैराग्राफ से शुरू करते हैं। वास्तव में, हमें एक नए ऑब्जेक्ट को वापस करने की आवश्यकता नहीं है, जबकि आह्वान के माध्यम से अंतिम फ़ंक्शन के आह्वान को जटिल करता है। हम इस तथ्य का लाभ उठा सकते हैं कि जावास्क्रिप्ट में एक फ़ंक्शन एक ऑब्जेक्ट है, जिसका अर्थ है कि इसमें अपने स्वयं के क्षेत्र और विधियां हो सकती हैं। आइए अपना रिटर्न df फ़ंक्शन बनाएं और इसके लिए getCalls विधि जोड़ें, जिसमें क्लोजर के माध्यम से पहले की तरह कॉल का उपयोग होगा:
function count(f) { let calls = 0; function df() { calls += 1; return f(...arguments); } df.getCalls = function() { return calls; } return df; }
इसके साथ काम करना अधिक सुखद है:
const csum = count((x, y) => x + y); csum(3, 7);
चौथा बिंदु स्पष्ट है: हम फंक्शनएक्सप्रेशन को एरोफ़ंक्शन के साथ बदल देते हैं। घुंघराले ब्रेसिज़ की अनुपस्थिति हमें उसके शरीर में एकल अभिव्यक्ति के मामले में तीर फ़ंक्शन का एक छोटा रिकॉर्ड प्रदान करेगी:
function count(f) { let calls = 0; function df() { calls += 1; return f(...arguments); } df.getCalls = () => calls; return df; }
तीसरे के साथ - सब कुछ अधिक जटिल है। याद रखें कि हमने जो पहली चीज़ की थी, उसे फंक्शन एक्सप्रेशन के साथ फंक्शन डिक्लेरेशन डीएफ के साथ लाया गया था । एरोफंक्शन के लिए इसे फिर से लिखने के लिए , दो समस्याओं को हल करना होगा: तर्कों तक पहुंच नहीं खोना (अब यह तर्कों का छद्म-सरणी है) और दो अभिव्यक्तियों के फ़ंक्शन बॉडी से बचने के लिए।
पहली समस्या हमें प्रसार ऑपरेटर के साथ फ़ंक्शन पैरामीटर आर्ग के लिए स्पष्ट रूप से निर्दिष्ट करने में मदद करेगी। और दो भावों को एक में मिलाने के लिए, आप तार्किक और का उपयोग कर सकते हैं। बूलियन को लौटाने वाले शास्त्रीय तार्किक संयोजन ऑपरेटर के विपरीत, यह पहले "झूठे" के लिए बाएं से दाएं ऑपरेंड की गणना करता है और इसे वापस करता है, और यदि सभी "सच" हैं - तो अंतिम मूल्य। काउंटर की बहुत पहली वृद्धि हमें 1 देगी, जिसका अर्थ है कि यह उप-अभिव्यक्ति हमेशा सच हो जाएगी। दूसरे उप-अभिव्यक्ति में फ़ंक्शन कॉल के परिणाम के "सत्य" के लिए अतिरेक हमें रुचि नहीं देता है: किसी भी मामले में, कैलकुलेटर इसे बंद कर देता है। अब हम ArrowFunction का उपयोग कर सकते हैं:
function count(f) { let calls = 0; let df = (...args) => (calls += 1) && f(...args); df.getCalls = () => calls; return df; }
आप उपसर्ग वृद्धि के उपयोग से रिकॉर्ड को थोड़ा सा सजा सकते हैं:
function count(f) { let calls = 0; let df = (...args) => ++calls && f(...args); df.getCalls = () => calls; return df; }
पहले और सबसे कठिन बिंदु का समाधान एरोफ़ंक्शन के साथ फ़ंक्शनडक्लेरेशन को प्रतिस्थापित करके शुरू होगा। लेकिन हमारे शरीर में अभी भी घुंघराले ब्रेसिज़ हैं:
const count = f => { let calls = 0; let df = (...args) => ++calls && f(...args); df.getCalls = () => calls; return df; };
यदि हम फ़ंक्शन के शरीर को घुंघराले ब्रेसिज़ से छुटकारा चाहते हैं, तो हमें चर घोषित करने और शुरू करने से बचना होगा। और हमारे पास दो पूरे चर हैं: कॉल और डीएफ ।
पहले, चलो काउंटर से निपटते हैं। हम इसे फंक्शन पैरामीटर्स की सूची में परिभाषित करके एक स्थानीय वैरिएबल बना सकते हैं, और इसे IIFE (तत्काल इनवॉइस फंक्शन एक्सप्रेशन) का उपयोग करके कॉल करके प्रारंभिक मान ट्रांसफर कर सकते हैं:
const count = f => (calls => { let df = (...args) => ++calls && f(...args); df.getCalls = () => calls; return df; })(0);
यह तीनों भावों को एक में समाहित करता है। चूँकि हमारे पास सभी तीन अभिव्यक्तियाँ हैं जो ऐसे कार्यों का प्रतिनिधित्व करती हैं जो हमेशा सत्य के प्रति कमनीय होते हैं, हम तार्किक और का उपयोग भी कर सकते हैं:
const count = f => (calls => (df = (...args) => ++calls && f(...args)) && (df.getCalls = () => calls) && df)(0);
लेकिन अभिव्यक्तियों को समझने के लिए एक और विकल्प है: अल्पविराम ऑपरेटर का उपयोग करना। यह बेहतर है, क्योंकि यह अनावश्यक तार्किक परिवर्तनों से नहीं निपटता है और कम कोष्ठक की आवश्यकता होती है। ऑपरेंड का मूल्यांकन बाएं से दाएं किया जाता है, और परिणाम बाद के मूल्य का होता है:
const count = f => (calls => (df = (...args) => ++calls && f(...args), df.getCalls = () => calls, df))(0);
मुझे लगता है कि मैं आपको बेवकूफ बनाने में कामयाब रहा? हमने साहसपूर्वक df वैरिएबल की घोषणा से छुटकारा पा लिया और केवल अपने एरो फ़ंक्शन के असाइनमेंट को छोड़ दिया। इस मामले में, इस चर को विश्व स्तर पर घोषित किया जाएगा, जो अस्वीकार्य है! Df के लिए , हम अपने IIFE फ़ंक्शन के मापदंडों में स्थानीय चर के आरंभीकरण को दोहराते हैं, लेकिन हम किसी प्रारंभिक चरण को पास नहीं करेंगे:
const count = f => ((calls, df) => (df = (...args) => ++calls && f(...args), df.getCalls = () => calls, df))(0);
इस प्रकार लक्ष्य प्राप्त होता है।
एक थीम पर बदलाव
दिलचस्प है, हम स्थानीय चर बनाने, और फ़ंक्शन ब्लॉकों में कई अभिव्यक्तियों को बनाने और एक वस्तु शाब्दिक बनाने से बचने में सक्षम थे। उसी समय, मूल समाधान को साफ रखा गया था: वैश्विक चर की अनुपस्थिति, काउंटर गोपनीयता, लिपटे जा रहे फ़ंक्शन के तर्कों तक पहुंच।
सामान्य तौर पर, आप किसी भी कार्यान्वयन को ले सकते हैं और कुछ समान करने की कोशिश कर सकते हैं। उदाहरण के लिए, इस संबंध में बाइंड फ़ंक्शन के लिए पॉलीफिल काफी सरल है:
const bind = (f, ctx, ...a) => (...args) => f.apply(ctx, a.concat(args));
हालाँकि, यदि तर्क f कोई फ़ंक्शन नहीं है, तो हमें एक अपवाद को अच्छे तरीके से फेंकना चाहिए। और फेंक अपवाद अभिव्यक्ति के संदर्भ में नहीं फेंका जा सकता है। आप फेंक अभिव्यक्ति (चरण 2) के लिए इंतजार कर सकते हैं और फिर से कोशिश कर सकते हैं। या किसी के पास पहले से ही विचार हैं?
या एक वर्ग पर विचार करें जो एक बिंदु के निर्देशांक का वर्णन करता है:
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return `(${this.x}, ${this.y})`; } }
जिसे एक फ़ंक्शन द्वारा दर्शाया जा सकता है:
const point = (x, y) => (p => (px = x, py = y, p.toString = () => ['(', x, ', ', y, ')'].join(''), p))(new Object);
केवल यहां हमने प्रोटोटाइप इनहेरिटेंस खो दिया है: स्ट्रींग प्वाइंट प्रोटोटाइप ऑब्जेक्ट की एक संपत्ति है, न कि एक अलग से बनाई गई वस्तु। यदि आप कठिन प्रयास करते हैं तो क्या इससे बचा जा सकता है?
परिवर्तनों के परिणामों में, हमें अनिवार्य हैक और भाषा की कुछ विशेषताओं के साथ कार्यात्मक प्रोग्रामिंग का अस्वास्थ्यकर मिश्रण मिलता है। यदि आप इसके बारे में सोचते हैं, तो यह स्रोत कोड का एक दिलचस्प (लेकिन व्यावहारिक नहीं) पर्यवेक्षक बन सकता है। आप "ब्रैकेटिंग ऑबफ्यूजेटर" कार्य के अपने संस्करण के साथ आ सकते हैं और उपयोगी कार्यों से अपने खाली समय में सहयोगियों और जावास्क्रिप्ट के दोस्तों का मनोरंजन कर सकते हैं।
निष्कर्ष
सवाल यह है कि यह किसके लिए उपयोगी है और इसकी आवश्यकता क्यों है? यह शुरुआती लोगों के लिए पूरी तरह से हानिकारक है, क्योंकि यह भाषा की अत्यधिक जटिलता और विचलन का एक गलत विचार बनाता है। लेकिन यह चिकित्सकों के लिए उपयोगी हो सकता है, क्योंकि यह आपको दूसरी तरफ से भाषा की विशेषताओं को देखने की अनुमति देता है: कॉल से बचने के लिए नहीं है, और कॉल भविष्य में बचने की कोशिश करने के लिए है।