YAMD: जेएस में मॉड्यूल का वर्णन करने के लिए एक और बाइक

हाल ही में, मैंने JS में बहुत कुछ लिखना शुरू किया, अब मैं एक जटिल एप्लिकेशन और एक बड़ी लाइब्रेरी (~ 5K SLoC) पर काम कर रहा हूं। निश्चित रूप से, मैं प्रतिरूपकता की समस्या में भाग गया।

एएमडी आवेदन के लिए आदर्श रूप से अनुकूल था - आप इसे पुस्तकालय निर्भरता में निर्दिष्ट करते हैं, कनेक्टिंग कोड, तर्क जोड़ते हैं ... और आवेदन तैयार है। लेकिन पुस्तकालय को विकसित करते समय, मैं एएमडी या कॉमनजेस का उपयोग करके आंतरिक निर्भरता के प्रबंधन की समस्या में भाग गया - मुझे बहुत सारे बॉयलरप्लेट मिलते हैं , खासकर जब पुस्तकालय के कुछ हिस्सों पर निर्भरता होती है । इसलिए, मैंने JS - YAMD में मॉड्यूल की परिभाषा के लिए एक और दृष्टिकोण पर प्रकाश डाला।

चेतावनी! यह एएमडी या कॉमनजेस के लिए एक प्रतिस्थापन नहीं है, मैं अभी भी एएमडी का उपयोग एप्लिकेशन का निर्माण करने के लिए करता हूं, सिर्फ एक पुस्तकालय जो मैं कनेक्ट करता हूं, वह YAMD का उपयोग करके बनाया गया है। इस प्रकार, YAMD बाहरी पुर्जों और अलग-अलग फ़ाइलों में एक जटिल पुस्तकालय के अपघटन के लिए एक दृष्टिकोण है, और इन फ़ाइलों को एक साथ इकट्ठा करने के लिए एक उपकरण है।

लेख में मैं दृष्टिकोण का वर्णन करूंगा। आपसे मैं टिप्पणियों में जानना चाहता हूं कि आप उन्हीं कार्यों के लिए क्या उपयोग करते हैं।

YAMD


जावास्क्रिप्ट के लिए मॉड्यूल को परिभाषित करने के लिए एक और दृष्टिकोण। CommonJS और AMD के विपरीत:

YAMD अलग-अलग फ़ाइलों में अपनी कार्यक्षमता के अपघटन और इन फ़ाइलों के बाद की असेंबली को एक साथ बनाने के लिए एक दृष्टिकोण है। आउटपुट फ़ाइल को IIFE के रूप में निष्पादित किया जा सकता है, एक नाम (पुस्तकालय का नाम) को वैश्विक दायरे में लाया जा सकता है, इसलिए एक कॉमनजस या एएमडी आवरण के रूप में।

मैंने इस सिद्धांत का पालन किया कि पुस्तकालय विकसित करते समय YAMD का उपयोग पुस्तकालय उपयोगकर्ता पर प्रतिबंध या दायित्वों को लागू नहीं करना चाहिए।

YAMD का उपयोग करने पर समझ में आता है:

मेरे लिए यह समझना आसान है कि जब मैं पहले से ही जानता हूं, तो एक सादृश्य के साथ एक सादृश्य खींचा जाता है, इसलिए एएमडी और कॉमनज के साथ क्रॉस-तुलना के माध्यम से यमद का वर्णन दिया जाएगा।

YAMD, AMD और CommonJS की तुलना


एक साधारण उदाहरण का उपयोग करते हुए, YAMD, AMD और कॉमनज की तुलना करें। कल्पना कीजिए कि हम एक गणित पुस्तकालय लिख रहे हैं।

हम अलग-अलग फ़ाइलों में पुस्तकालय कार्यों का चयन करते हैं, इसलिए सभी दृष्टिकोणों के लिए स्रोत निर्देशिका समान होगी:
> find . ./math ./math/multiply.js ./math/add.js 

स्रोत पर नजर डालते हैं:
एएमडीCommonJSYAMD
./math/add.js
 define([], function() { return function(a,b) { return a + b; }; }); 
 function add(a, b) { return a + b; } module.exports = add; 
 expose(add); function add(a, b) { return a + b; } 
./math/multiply.js
 define( ["math/adding"], function(adding) { return function(a,b) { var result = 0; for (var i=0;i<a;i++) { result = adding(result, b); } return result; }; }); 
 var add = require('./add'); function multiply(a,b) { var result = 0; for (var i=0;i<a;i++) { result = add(result, b); } return result; } module.exports = multiply; 
 expose(multiply); function multiply(a,b) { var result = 0; for (var i=0;i<a;i++) { result = root.add(result, b); } return result; } 
उपयोग (सभी आश्रितों को शामिल करते हुए)
 require( ["math/add", "math/multiply"], function(add, multiply) { console.info(add(2,7)); console.info(multiply(2,7)); }); 
 var multiply = require("./math/multiply"); var add = require("./math/add"); console.info(add(7,2)); console.info(multiply(7,2)); 
 console.info(math.add(7,2)); console.info(math.multiply(7,2)); 

एएमडी दृष्टिकोण बल्कि क्रियात्मक निकला, कॉमन जेजेएस फ़ंक्शन में प्रत्येक फ़ाइल को स्पष्ट रूप से लपेटकर बाइंडिंग की संख्या को कम करता है, और YAMD इसे एक कदम आगे ले जाता है - रूट लाइब्रेरी की जड़ का परिचय देता है, जिसके माध्यम से आप स्पष्ट आयात के बिना इसके किसी भी हिस्से तक पहुंच सकते हैं।

YAMD के साथ काम करें


YAMD शैली में डिज़ाइन की गई लाइब्रेरी का निर्माण करने के लिए, आपको python yamd.py path/to/library को चलाने की आवश्यकता है - परिणामस्वरूप, मौजूदा निर्देशिका में nameOfTheLibrary.js फ़ाइल दिखाई देगी। लाइब्रेरी का नाम स्रोत निर्देशिका के नाम से निर्धारित होता है, इसके अलावा, इस नाम का उपयोग लाइब्रेरी को वैश्विक दायरे में जोड़ने के लिए किया जाता है (जब तक कि, निश्चित रूप से विधानसभा कॉमनजेस या एएमडी मॉड्यूल में निर्दिष्ट हो)।

जेएस में चर नामों पर प्रतिबंध के संदर्भ में निर्देशिका का नाम, साथ ही सभी उपनिर्देशिकाओं और जेएस फाइलों के नाम (".js" से पहले) मान्य होना चाहिए।

निर्देशिका पदानुक्रम मॉड्यूल के पदानुक्रम को परिभाषित करता है, और js-files इन मॉड्यूल को फ़ंक्शन (निर्माणकर्ता) से भरता है - आपको जावा या नामस्थान और C # में संकुल और कक्षाएं जैसी कुछ मिलता है।

मॉड्यूल में एक फ़ंक्शन जोड़ने के लिए, आपको इस मॉड्यूल (इसी से पहले फ़ाइल नाम .js फ़ंक्शन नाम सेट करता है) के अनुरूप निर्देशिका में एक js- फ़ाइल बनाने की आवश्यकता है, उदाहरण के add , इसमें किसी भी नाम के साथ एक फ़ंक्शन को परिभाषित करें, और फ़ाइल की शुरुआत में इसे बेनकाब करें, इसे पास करें। एक फ़ंक्शन, उदाहरण के लिए, `एक्सपोज़ (जोड़ें);`। अन्य सभी फ़ाइल सामग्री निजी होगी और केवल निर्यात किए गए फ़ंक्शन के लिए दिखाई देगी।

यह अजीब लग सकता है कि घोषणा से पहले फ़ंक्शन का उपयोग किया जाता है - expose(add); , लेकिन यह YAMD जादू नहीं है, लेकिन जेएस के लिए कानूनी व्यवहार लहरा रहा है। फिर भी, एक आवश्यकता है कि फ़ाइल की शुरुआत में जाना expose , केवल एक बार होता है, और इसे कॉल करने से पहले, root करने के लिए एक भी कॉल नहीं था।

असेंबली के बाद का पिछला उदाहरण (गणित पुस्तकालय) निम्नलिखित कोड के बराबर होगा:
 var math = (function(){ var root = { add: function(a, b) { return a + b; }, multiply: function(a,b) { var result = 0; for (var i=0;i<a;i++) { result = root.add(result, b); } return result; } }; return root; })(); 

मान लीजिए कि हम अपने पुस्तकालय को जटिल बनाने का निर्णय लेते हैं और इसमें प्रमेय से वितरण जोड़ते हैं। उन्हें अलग मॉड्यूल (निर्देशिका) में रखना तर्कसंगत है, परिवर्तनों के बाद, स्रोत निर्देशिका इस तरह दिखती है:
 > find . ./math ./math/multiply.js ./math/add.js ./math/distributions ./math/distributions/normal.js ./math/distributions/bernoulli.js 

फिर विधानसभा के बाद हमें लगभग निम्नलिखित कोड मिलते हैं
 var math = (function(){ var root = { add: function(a, b) { return a + b; }, multiply: function(a,b) { var result = 0; for (var i=0;i<a;i++) { result = root.add(result, b); } return result; }, distributions: { normal: function() { throw new Error("TODO"); }, bernoulli: function() { throw new Error("TODO"); } } }; return root; })(); 

फ़ंक्शन के अतिरिक्त, `एक्सपोज़` पर लौटना, यह निश्चित रूप से मॉड्यूल को स्ट्रिंग्स, संख्या या ऑब्जेक्ट निर्यात कर सकता है। यह पता चलता है कि हम लाइब्रेरी के मूल में सभी डिस्ट्रीब्यूशन को एक फाइल में रखकर एक अलग निर्देशिका बनाने के बजाय पिछले उदाहरण को फिर से लिख सकते हैं:
 // FILE ./math/distributions.js expose({normal: normal, bernoulli: bernoulli}); function normal() { throw new Error("TODO"); } function bernoulli() { throw new Error("TODO"); } 

विधानसभा के बाद, पुस्तकालय पूरी तरह से समकक्ष होंगे।

पारस्परिक रूप से पुनरावर्ती मॉड्यूल


YAMD में, एक मॉड्यूल में एक फ़ंक्शन जोड़ना संभव है जो दूसरे मॉड्यूल के फ़ंक्शन का उपयोग करता है, पहला। हालांकि, कॉमनजस और एएमडी के मामले में, यह भी संभव है, अंतर केवल कोड की संख्या में है। उदाहरण के लिए, हम एक फ़ंक्शन लिखते हैं जो Collatz प्रक्रिया में चरणों की संख्या की गणना करता है । पहले उदाहरण के रूप में, एएमडी, कॉमनज और यमद के मामले में निर्देशिका संरचना नहीं बदलेगी:
 > find math/collatz math/collatz math/collatz/steps.js math/collatz/inc.js math/collatz/dec.js 

और अब कोड:
एएमडीCommonJSYAMD
./math/collatz/steps.js
 define( ["require", "math/collatz/inc", "math/collatz/dec"], function(require, inc, dec) { return function(n) { if (n==1) return 0; if (n%2==0) return require("math/collatz/dec")(n); if (n%2==1) return require("math/collatz/inc")(n); }; }); 
 function steps(n) { if (n==1) return 0; if (n%2==0) return require('./dec')(n); if (n%2==1) return require('./inc')(n); } module.exports = steps; 
 expose(steps); function steps(n) { if (n==1) return 0; if (n%2==0) return root.collatz.dec(n); if (n%2==1) return root.collatz.inc(n); } 
./math/collatz/inc.js
 define( ["require", "math/collatz/steps"], function(require, steps) { return function(n) { return require("math/collatz/steps")(3*n+1)+1; }; }); 
 function inc(n) { return require('./steps')(3*n+1)+1; } module.exports = inc; 
 expose(inc) function inc(n) { return root.collatz.steps(3*n+1)+1; } 
./math/collatz/dec.js
 define( ["require", "math/collatz/steps"], function(require, steps) { return function(n) { return require("math/collatz/steps")(n/2)+1; }; }); 
 function dec(n) { return require('./steps')(n/2)+1; } module.exports = dec; 
 expose(dec) function dec(n) { return root.collatz.steps(n/2)+1; } 

एएमडी के मामले में पारस्परिक निर्भरता इस दस्तावेज़ के अनुसार वर्णित की गई थी - हमें आवश्यकता पर निर्भरता जोड़ना था और कार्यों के अंदर निर्भरता को स्पष्ट रूप से आयात करने के लिए इसका उपयोग करना था।

इस उदाहरण को सभी तीन दृष्टिकोणों द्वारा नियंत्रित किया गया था, लेकिन यह अपेक्षाकृत सरल है - पुनरावर्ती प्रकृति केवल तभी बाहर निकलती है जब पुस्तकालय कार्यों को उपयोगकर्ताओं द्वारा बुलाया जाता है, और इस समय तक पुस्तकालय पहले ही लोड हो चुका होता है। यदि लाइब्रेरी को प्रारंभ करते समय आपको लाइब्रेरी के कार्यों का उपयोग करने की आवश्यकता होती है तो एक पारस्परिक पुनरावृत्ति समस्या उत्पन्न होती है। टॉम की पोस्ट में इस मुद्दे को अच्छी तरह से समझा गया है।

इसका मुकाबला करने के लिए, विलंबित आरंभीकरण को expose में जोड़ा गया था: expose एक फ़ंक्शन (मॉड्यूल कंस्ट्रक्टर) को पास करने के expose दूसरे तर्क का उपयोग किया जा सकता है, जिसके लिए यह गारंटी है कि यह सभी मॉड्यूल लोड होने के बाद कहा जाएगा।

अपने उदाहरण पर लौटते हुए, मान लीजिए कि हम steps को गति देने का निर्णय लेते हैं और कुछ के लिए पुस्तकालय को लोड करते समय चरणों की संख्या की गणना करते हैं। हमें एक tableLookup डेकोरेटर tableLookup

 function tableLookup(table, f) { return function(n) { if (n in table) return table[n]; return f(n); } } 

तब हमें केवल YAMD दृष्टिकोण में step.js फ़ाइल को संशोधित करने की आवश्यकता है:
 // FILE ./math/collatz/steps.js var table = {}; expose(tableLookup(table, steps), ctor); function ctor() { table[3] = steps(3); } function steps(n) { if (n==1) return 0; if (n%2==0) return root.collatz.dec(n); if (n%2==1) return root.collatz.inc(n); } 

CommonJS / AMD का उपयोग करते समय, हमारे पास एक ही चीज़ को लागू करने के दो तरीके हैं:

यह बुरी तरह से बदल जाता है - कॉमनजस में, इस समस्या को हल करने के लिए, हमें या तो एपीआई बदलना होगा या एकल जिम्मेदारी सिद्धांत का उल्लंघन करना चाहिए और चरणों में आलस्य नियंत्रण जोड़ना होगा:
 // FILE ./math/collatz/steps.js var table = {}; var inited = false; function ctor() { table[3] = steps(3); } function steps(n) { if (!inited) { ctor(); inited = true } if (n==1) return 0; if (n%2==0) return require('./dec')(n); if (n%2==1) return require('./inc')(n); } module.exports = tableLookup(table, steps) 

यदि आप कॉमन जेजेएस में एसआरपी उल्लंघन के लिए अपनी आँखें बंद कर लेते हैं और कठिन आरंभीकरण प्रक्रियाओं पर विचार करते हैं, तो दोनों विकल्प खराब ब्रेक हैं, यमद के मामले में, लाइब्रेरी से कनेक्ट होने पर ब्रेक, और कॉमनजस के मामले में, ब्रेक आपको पहले steps । लेकिन आरंभीकरण हमेशा मुश्किल नहीं होता है, और यदि यह सब समान है, तो YAMD का उपयोग करके आप इसे वेबवेयर को ctor से लॉन्च की गई प्रक्रियाओं में स्थानांतरित करने का प्रयास कर सकते हैं और आशा करते हैं कि यह पहले से ही `स्टेप्स` के पहले रन से समाप्त हो जाएगा। हम कॉमनजेएस का भी उपयोग नहीं कर सकते हैं, क्योंकि हमारे पास केवल पहले अनुरोध पर प्रारंभ करने का मौका होगा, इसलिए इस अनुरोध की गणना करने का समय नहीं होगा और धीमा होने की गारंटी होगी।

आपका ध्यान देने के लिए धन्यवाद। और टिप्पणियों में लिखना न भूलें कि आप जटिल जेएस लाइब्रेरी कैसे विकसित करते हैं।

Source: https://habr.com/ru/post/In190700/


All Articles