-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path10_modules.html
More file actions
376 lines (229 loc) · 73.5 KB
/
10_modules.html
File metadata and controls
376 lines (229 loc) · 73.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ماژولها :: Eloquent JavaScript</title>
<link rel=stylesheet href="js/node_modules/codemirror/lib/codemirror.css">
<script src="js/acorn_codemirror.js"></script>
<link rel=stylesheet href="css/ejs.css">
<script src="js/sandbox.js"></script>
<script src="js/ejs.js"></script><script>var chapNum = 10;var sandboxLoadFiles = ["code/packages_chapter_10.js","code/chapter/07_robot.js"];</script><script>var clicky_site_ids = clicky_site_ids || []; clicky_site_ids.push(101171577);</script>
<script async src="//static.getclicky.com/js"></script>
<script id='pixel-script-poptin' src='https://cdn.popt.in/pixel.js?id=ea6051b792008' async='true'></script>
</head>
<article>
<nav><a href="09_regexp.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="11_async.html" title="next chapter">▶</a></nav>
<h1><span class=chap_num>فصل 10</span>ماژولها</h1>
<blockquote>
<p><a class="p_ident" id="p_2jmj7l5rSw" href="#p_2jmj7l5rSw" tabindex="-1" role="presentation"></a>کدی بنویسید که راحت بتوان در صورت نیاز حذفش کرد، لازم نیست قابل توسعه باشد.</p>
<footer>تیف, <cite>برنامهنویسی وحشتناک است</cite></footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_10.jpg" alt="Picture of a building built from modular pieces"></figure>
<p>یک برنامهی ایدهآل دارای ساختاری شفاف و روشن است. به راحتی میتوان کارکرد آن را توضیح داد و هر بخش از آن نقشی را ایفا میکند که به خوبی تعریف شده است.</p>
<p>معمولا یک برنامهی واقعی به شکلی ارگانیک رشد میکند. قابلیتهای جدید، همانطور که لازم میشوند به برنامه افزوده میشوند. ساختاردهی – و حفظ ساختار – کار مجزایی است. کاری است که فقط در آینده، زمانی که کسی دوباره روی برنامه قرار است کار کند، با مزایای آن روبرو میشوید. پس ممکن است وسوسه شوید که از آن غفلت کنید و بگذارید بخشهای برنامه عمیقا دچار آشفتگی شوند.</p>
<p>در عمل این غفلت دو اشکال ایجاد میکند. اول اینکه درک یک سیستم بدون ساختار مشکل است. اگر همهی بخشهای برنامه در تماس با دیگر بخشها باشند، سخت میتوان بخشی از برنامه را به صورت جداگانه بررسی نمود. شما باید درکی کلی و جامع از برنامه داشته باشید. دوم اینکه، اگر بخواهید هر کدام از قابلیتهای برنامهای اینچنینی را در جایی دیگر استفاده کنید، از اول نوشتن آن قابلیت ممکن است از جداسازی آن از برنامه، آسانتر باشد.</p>
<p><a class="p_ident" id="p_yhmJYha4k4" href="#p_yhmJYha4k4" tabindex="-1" role="presentation"></a>اصطلاح توپ بزرگ گلی (“big ball of mud”)، اغلب برای برنامههای بزرگی که ساختاری ندارند استفاده میشود. همه چیز به هم چسبیده است و زمانی که قصد دارید یک قسمت را جدا کنید، کل آن قسمت یا برنامه متلاشی میشود و دستانتان را کثیف میکند.</p>
<h2><a class="h_ident" id="h_3Uyo5Wx/hs" href="#h_3Uyo5Wx/hs" tabindex="-1" role="presentation"></a>ماژولها</h2>
<p>استفاده از <em>ماژولها</em> تلاشی برای اجتناب از این گونه مشکلات است. یک ماژول بخشی از برنامه است که مشخص میکند به کدام بخشهای دیگر از برنامه وابسته است (وابستگیهای آن) و چه قابلیتی برای استفادهی دیگر ماژولها فراهم میکند (<em>رابط</em> آن).</p>
<p>رابطهای ماژول شباهت زیادی با رابطهای شیء دارند، همانطور که با آنها در <a href="06_object.html#interface">فصل 6</a> آشنا شدیم. رابطها بخشی از ماژول را در دسترس جهان بیرون میگذارند و بقیهی قسمتها را به صورت خصوصی حفظ میکنند. با محدودسازی راههای تعامل ماژولها با یکدیگر، سیستم بیشتر شبیه لگو میشود، جایی که قطعات توسط متصلکنندههایی که به خوبی تعریف شدهاند با هم تعامل دارند و کمتر به توپ گلی شباهت خواهند داشت که همه چیز در آن با هم مخلوط شده است.</p>
<p><a class="p_ident" id="p_3ljisXwZYA" href="#p_3ljisXwZYA" tabindex="-1" role="presentation"></a>ارتباطات بین ماژولها را <em>وابستگیها</em> (dependency)مینامند. زمانی که یک ماژول به بخشی از یک ماژول دیگر نیاز دارد، گفته میشود که به آن ماژول وابستگی دارد. زمانی که این وابستگی در خود ماژول به صورت مشخص اعلام شود، میتوان از آن برای شناسایی دیگر ماژولهایی که لازم است برای اجرای یک ماژول خاص حضور داشته باشند استفاده کرد و به صورت خودکار آن وابستگیها را بارگیری کرد.</p>
<p><a class="p_ident" id="p_qs0xA/Px/K" href="#p_qs0xA/Px/K" tabindex="-1" role="presentation"></a>برای جداسازی ماژولها به این روش، لازم است هر کدام از آنها، قلمروی (scope) خصوصی خودشان را داشته باشند.</p>
<p>فقط قرار دادن کدهای جاوااسکریپت در فایلهای جداگانه این امکان را فراهم نمیکند. فایلها همچنان فضای نام سراسری یکسانی را به صورت مشترک استفاده میکنند. ممکن است به صورت تصادفی یا آگاهانه بین متغیرهای یکدیگر تداخل ایجاد کنند و ساختار وابستگی همچنان غیر شفاف خواهد ماند. میتوان کار بهتری انجام داد که در ادامه خواهیم دید.</p>
<p>طراحی یک ساختار ماژول مناسب برای یک برنامه ممکن است سخت باشد. در مرحلهای که هنوز در حال بررسی مشکل هستید، و چیزهای متفاوتی را آزمایش میکنید، ممکن است علاقهای نداشته باشید که زیاد به ساختاردهی فکر کنید، چرا که میتواند باعث حواسپرتی زیادی بشود. زمانی که به نتیجهای استوار و قابل اتکا رسیدید، وقت مناسب برای اقدام جهت سازماندهی برنامه فرا رسیده است.</p>
<h2><a class="h_ident" id="h_gMMd6GoKDy" href="#h_gMMd6GoKDy" tabindex="-1" role="presentation"></a>بستهها (Packages)</h2>
<p>یکی از مزایای ساختن یک برنامه بر اساس قسمتهای جداگانه، و داشتن قابلیت اجرای آن قسمتها به صورت مستقل، این است که ممکن است بتوانید آن قسمتها را در برنامههای مختلف به کار ببرید.</p>
<p>اما چگونه آن را راهاندازی میکنید؟ فرض کنیم من میخواهم که تابع <code>parseINI</code> را که در <a href="09_regexp.html#ini">فصل 9</a> نوشتیم در برنامهی دیگری استفاده کنم. اگر روشن باشد که این تابع چه وابستگیهایی دارد (که در اینجا ندارد)، میتوانم به سادگی کدهای مورد نیاز را به پروژهی جدیدم کپی کنم و از آن استفاده کنم. اما در این صورت اگر مشکلی در آن کد پیدا کنم، احتمالا آن مشکل را فقط در برنامهای که در حال کار روی آن هستم رفع خواهم کرد و فراموش میکنم که در برنامهی دیگر نیز آن را اصلاح کنم.</p>
<p>به محض اینکه به کپی کردن کدها اقدام کنید متوجه خواهید شد که با جابجا کردن کپیها بین برنامهها و به روزرسانی آنها وقت و انرژی خودتان را تلف میکنید.</p>
<p>اینجا است که بستهها کاربرد خواهند داشت. یک بسته یک قطعه کد است که میتواند توزیع شود (کپی و نصب شود). یک بسته میتواند دارای یک یا چند ماژول باشد و همچنین اطلاعاتی دربارهی دیگر بستههایی که به آنها وابسته است را خواهد داشت. یک بسته همچنین همراه با مستنداتی میآید که کارکرد آن را شرح میدهد، بنابراین کسانی که بسته را ننوشتهاند قادر خواهند بود که از آن استفاده کنند.</p>
<p>زمانی که مشکلی در یک بسته شناسایی شود یا ویژگی جدیدی به آن افزوده شود بسته به روزرسانی میگردد. اکنون برنامههایی که به آن وابستگی داشتهاند (که خود ممکن است بسته باشند) میتوانند به نسخه جدیدتر بهروز شوند.</p>
<p id="modules_npm"><a class="p_ident" id="p_HN/3W2IVS2" href="#p_HN/3W2IVS2" tabindex="-1" role="presentation"></a>کارکردن به این روش نیاز به زیرساخت دارد. جایی را نیاز داریم که بستهها را ذخیره و جستجو کنیم و راهی سرراست برای نصب و به روزرسانی آنها باید وجود داشته باشد. در دنیای جاوااسکریپت این زیرساخت توسط NPM در (<a href="https://npmjs.org"><em>https://npmjs.org</em></a>) فراهم شده است.</p>
<p><a class="p_ident" id="p_2Ehj08AuWd" href="#p_2Ehj08AuWd" tabindex="-1" role="presentation"></a>NPM دارای دو بخش است: یک سرویس آنلاین که افراد میتوانند به بارگیری و بارگذاری بستهها اقدام کنند و یک برنامه (که به همراه Node.js میآید) که به شما کمک میکند که آنها را مدیریت کنید.</p>
<p><a class="p_ident" id="p_E1MBXEc91D" href="#p_E1MBXEc91D" tabindex="-1" role="presentation"></a>در زمان نوشتن این کتاب، بیش از نیم میلیون بسته در NPM وجود دارد. باید اشاره کنم که بخش زیادی از این بستهها دیگر کاربردی ندارند اما تقریبا هر بستهی مفیدی که عموما در دسترس باشد را میتوان آنجا پیدا کرد. به عنوان مثال، یک تجزیهگر فایل ini، شبیه به چیزی که خودمان در <a href="09_regexp.html">فصل 9</a> ساختیم، در بستهای به نام <code>ini</code> وجود دارد.</p>
<p><a href="20_node.html">فصل 20</a> شما را با نحوهی نصب بستهها به صورت محلی و با استفاده از برنامهی خط فرمان <code>npm</code> آشنا خواهد کرد.</p>
<p>در دسترس داشتن بستههای باکیفیت و قابل دانلود بسیار ارزشمند است. این بدین معنا است که میتوان از اختراع دوبارهی یک برنامه در بیشتر مواقع جلوگیری کنیم، برنامهای که صدها نفر پیشتر نوشتهاند و میتوان آن را با چند حرکت به صورت استوار و آزموده شده پیادهسازی کرد.</p>
<p>کپی کردن یک نرمافزار آسان است، بنابراین اگر کسی آن را نوشته باشد، انتشار آن بین دیگر افراد روند کارآمدی محسوب میشود. اما به عنوان فرد اول، نوشتن آن کار میبرد و پاسخ دادن به افرادی که مشکلاتی را در کد پیدا میکنند یا درخواست ویژگی جدیدی را دارند، کار بیشتری میطلبد.</p>
<p>به صورت پیش فرض، شما مالک کپیرایت کدی هستید که مینویسید و دیگر افراد فقط با اجازهی شما میتوانند از آن استفاده کنند. اما به دلیل اینکه بعضیها سخاوتمند هستند و اینکه انتشار یک نرمافزار خوب باعث کمی شهرت بین برنامهنویسان میشود، خیلی از بستهها تحت مجوزی منتشر میشوند که به صراحت اجازه استفاده دیگران از برنامه را میدهد.</p>
<p><a class="p_ident" id="p_fZTrvwf1QX" href="#p_fZTrvwf1QX" tabindex="-1" role="presentation"></a>بیشتر کدهایی که در NPM وجود دارند دارای چنین مجوزی هستند. بعضی از مجوزها از شما میخواهند که کدی که با استفاده از بسته نوشتهاید را با همان مجوز منتشر کنید. بعضی دیگر کمتر درخواستی دارند و فقط لازم است که مجوزی که همراه بسته وجود دارد را در هنگام توزیع کنار آن نگه دارید. بیشتر جامعهی برنامهنویسی جاوااسکریپت از این نوع مجوز استفاده میکنند. زمانی که از بستههای دیگر افراد استفاده میکنید، مطمئن شوید که از مجوز آن آگاهی دارید.</p>
<h2><a class="h_ident" id="h_/lUKS6kqAJ" href="#h_/lUKS6kqAJ" tabindex="-1" role="presentation"></a>فراهم ساختن ماژولها</h2>
<p><a class="p_ident" id="p_nN2mfe0/JY" href="#p_nN2mfe0/JY" tabindex="-1" role="presentation"></a>قبل از 2015 در جاوااسکریپت سیستم ماژول داخلی وجود نداشت. با این وجود برنامهنویسان برای بیشتر از یک دهه، سیستمهای بزرگی را برنامهنویسی میکردند درحالیکه <em>نیاز</em> به ماژولها وجود داشت.</p>
<p>بنابراین آنها سیستم ماژول خودشان را با استفاده از خود زبان طراحی کردند. میتوان از توابع جاوااسکریپت برای ایجاد قلمروهای محلی و از اشیاء به عنوان رابطهای ماژول استفاده کرد.</p>
<p>مثال زیر یک ماژول برای انتخاب بین نام روزها و عددشان است (که از متد <code>getDay</code> مربوط به <code>Date</code> استفاده میکند). رابط آن از <bdo><code>weekDay.name</code></bdo> و <bdo><code>weekDay.number</code></bdo> تشکیل شده است و متغیر محلی <code>names</code> را در قلمروی یک تابع که بلادرنگ فراخوانی میشود پنهان میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_m+yRMF5NXw" href="#c_m+yRMF5NXw" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">weekDay</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>() {
<span class="cm-keyword">const</span> <span class="cm-def">names</span> <span class="cm-operator">=</span> [<span class="cm-string">"Sunday"</span>, <span class="cm-string">"Monday"</span>, <span class="cm-string">"Tuesday"</span>, <span class="cm-string">"Wednesday"</span>,
<span class="cm-string">"Thursday"</span>, <span class="cm-string">"Friday"</span>, <span class="cm-string">"Saturday"</span>];
<span class="cm-keyword">return</span> {
<span class="cm-property">name</span>(<span class="cm-def">number</span>) { <span class="cm-keyword">return</span> <span class="cm-variable-2">names</span>[<span class="cm-variable-2">number</span>]; },
<span class="cm-property">number</span>(<span class="cm-def">name</span>) { <span class="cm-keyword">return</span> <span class="cm-variable-2">names</span>.<span class="cm-property">indexOf</span>(<span class="cm-variable-2">name</span>); }
};
}();
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">weekDay</span>.<span class="cm-property">name</span>(<span class="cm-variable">weekDay</span>.<span class="cm-property">number</span>(<span class="cm-string">"Sunday"</span>)));
<span class="cm-comment">// → Sunday</span></pre>
<p>این سبک از ماژولها، تا حدی ایزوله کردن را فراهم میکند اما وابستگی را پشتیبانی نمیکند. به جای آن، رابطش را در قلمروی سراسری قرار میدهد و انتظار دارد که در صورت وجود، وابستگیهایش تعریف شده باشند تا بتواند کاری مشابه سیستم وابستگیها انجام دهد. برای مدتی طولانی این روش در برنامهنویسی وب استفاده میشد اما الان تقریبا از رده خارج شده است.</p>
<p>اگر قصد دارید ارتباطات مربوط به وابستگی را به عنوان بخشی از کد داشته باشید، باید مدیریت بارگیری وابستگیها را به عهده بگیرید. برای این کار لازم است بتوانیم رشتهها را به عنوان کد اجرا کنیم. این کار در جاوااسکریپت قابل اجرا است.</p>
<h2 id="eval"><a class="h_ident" id="h_E7w8weTT/n" href="#h_E7w8weTT/n" tabindex="-1" role="presentation"></a>ارزیابی دادهها به عنوان کدهای اجرایی</h2>
<p>راههای متعددی برای گرفتن دادهها (یک رشته از کد) و اجرای آن به عنوان بخشی از برنامه کنونی وجود دارد.</p>
<p>روشنترین راه، استفاده از عملگر <code>eval</code> است که رشتهای را در قلمروی (scope) <em>فعلی</em> اجرا میکند. این ایده معمولا ایدهی بدی محسوب میشود چرا که باعث از بین رفتن بعضی از خاصیتهایی میشود که در قلمروها به صورت نرمال وجود دارند، مثل حدس زدن آسان متغیری که یک نام مشخص به آن ارجاع میدهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_14x3bOXX9G" href="#c_14x3bOXX9G" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">x</span> <span class="cm-operator">=</span> <span class="cm-number">1</span>;
<span class="cm-keyword">function</span> <span class="cm-def">evalAndReturnX</span>(<span class="cm-def">code</span>) {
<span class="cm-variable">eval</span>(<span class="cm-variable-2">code</span>);
<span class="cm-keyword">return</span> <span class="cm-variable">x</span>;
}
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">evalAndReturnX</span>(<span class="cm-string">"var x = 2"</span>));
<span class="cm-comment">// → 2</span>
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">x</span>);
<span class="cm-comment">// → 1</span></pre>
<p>یک راه کمخطرتر تفسیر داده به عنوان کد، استفاده از سازنده <code>Function</code> است. این تابع دو آرگومان دریافت میکند: یک رشته که حاوی یک لیست جدا شده با ویرگول از نام آرگومانها است و یک رشته که حاوی بدنه تابع است. این تابع کد را درون یک مقدار تابع میپوشاند بنابراین قلمروی مختص به خودش را خواهد داشت و تداخلی با دیگر قلمروها نخواهد داشت.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_Mc9BAi4AVK" href="#c_Mc9BAi4AVK" tabindex="-1" role="presentation"></a><span class="cm-keyword">let</span> <span class="cm-def">plusOne</span> <span class="cm-operator">=</span> <span class="cm-variable">Function</span>(<span class="cm-string">"n"</span>, <span class="cm-string">"return n + 1;"</span>);
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">plusOne</span>(<span class="cm-number">4</span>));
<span class="cm-comment">// → 5</span></pre>
<p>این دقیقا همان چیزی است که برای یک سیستم ماژول نیاز داریم. میتوانیم کدهای ماژول را درون یک تابع قرار دهیم و از قلمروی مربوط به تابع به عنوان قلمروی ماژول استفاده نماییم.</p>
<h2><a class="h_ident" id="h_N33QHgUxbG" href="#h_N33QHgUxbG" tabindex="-1" role="presentation"></a>CommonJS</h2>
<p id="commonjs"><a class="p_ident" id="p_FQuYAj9Pbx" href="#p_FQuYAj9Pbx" tabindex="-1" role="presentation"></a>رایجترین شیوهای که برای اضافه کردن ماژولها به جاوااسکریپت استفاده میشود، ماژولهای <em>CommonJS</em> میباشد. <bdo>Node.js</bdo> از آن استفاده میکند و سیستمی است که بیشتر بستههای NPM از آن استفاده میکنند.</p>
<p><a class="p_ident" id="p_N33QHgUxbG" href="#p_N33QHgUxbG" tabindex="-1" role="presentation"></a>مفهوم اصلی در ماژولهای CommonJS تابعی به نام <code>require</code> است. زمانی که از این تابع به همراه نام ماژول یک وابستگی استفاده میکنید، این تابع اطمینان حاصل میکند که ماژول مورد نظر بارگیری شده است و رابط آن را برمی گرداند.</p>
<p><a class="p_ident" id="p_xX4IryjIXn" href="#p_xX4IryjIXn" tabindex="-1" role="presentation"></a>به دلیل اینکه بارگیرنده (loader)، ماژول مورد نظر را درون یک تابع قرار میدهد، ماژولها به صورت خودکار قلمروی محلی خودشان را میگیرند. تنها کاری که باید بکنند این است که تابع <code>require</code> را فراخوانی کنند تا به وابستگیهای آنها دسترسی داشته باشند و رابطشان را در شیئی که به <code>exports</code> تخصیص یافته قرار دهند.</p>
<p><a class="p_ident" id="p_JTmQj6Vkmh" href="#p_JTmQj6Vkmh" tabindex="-1" role="presentation"></a>این ماژول به عنوان مثال، یک تابع ویرایش تاریخ را فراهم میسازد. اینجا از دو بسته از NPM استفاده میشود – بستهی <code>ordinal</code> برای تبدیل اعداد به رشتههایی شبیه <code>"1 st"</code> و <code>"2nd"</code> و بستهی <bdo><code>date-names</code></bdo> برای گرفتن نامهای انگلیسی روزهای هفته و ماهها. این ماژول یک تابع به نام <code>fomatDate</code> را صادر (export) میکند که یک شیء <code>Date</code> و یک رشته به عنوان قالب دریافت میکند.</p>
<p><a class="p_ident" id="p_zo2zifU/qh" href="#p_zo2zifU/qh" tabindex="-1" role="presentation"></a>رشتهی قالب میتواند حاوی کدهایی باشد که فرمت تاریخ را مشخص میکند مثل <code>YYYY</code> برای نمایش سال به صورت کامل و <code>Do</code> برای نامهای ترتیبی روزهای ماه. میتوانید به آن رشتهای به شکل <bdo><code>"MMMM Do YYYY"</code></bdo> برای دریافت چیزی شبیه <bdo>“November 22nd 2017”</bdo> ارسال کنید.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_hEFnba6fud" href="#c_hEFnba6fud" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">ordinal</span> <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"ordinal"</span>);
<span class="cm-keyword">const</span> {<span class="cm-def">days</span>, <span class="cm-def">months</span>} <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"date-names"</span>);
<span class="cm-variable">exports</span>.<span class="cm-property">formatDate</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">date</span>, <span class="cm-def">format</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable-2">format</span>.<span class="cm-property">replace</span>(<span class="cm-string-2">/YYYY|M(MMM)?|Do?|dddd/g</span>, <span class="cm-def">tag</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">tag</span> <span class="cm-operator">==</span> <span class="cm-string">"YYYY"</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">date</span>.<span class="cm-property">getFullYear</span>();
<span class="cm-keyword">if</span> (<span class="cm-variable-2">tag</span> <span class="cm-operator">==</span> <span class="cm-string">"M"</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">date</span>.<span class="cm-property">getMonth</span>();
<span class="cm-keyword">if</span> (<span class="cm-variable-2">tag</span> <span class="cm-operator">==</span> <span class="cm-string">"MMMM"</span>) <span class="cm-keyword">return</span> <span class="cm-variable">months</span>[<span class="cm-variable-2">date</span>.<span class="cm-property">getMonth</span>()];
<span class="cm-keyword">if</span> (<span class="cm-variable-2">tag</span> <span class="cm-operator">==</span> <span class="cm-string">"D"</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">date</span>.<span class="cm-property">getDate</span>();
<span class="cm-keyword">if</span> (<span class="cm-variable-2">tag</span> <span class="cm-operator">==</span> <span class="cm-string">"Do"</span>) <span class="cm-keyword">return</span> <span class="cm-variable">ordinal</span>(<span class="cm-variable-2">date</span>.<span class="cm-property">getDate</span>());
<span class="cm-keyword">if</span> (<span class="cm-variable-2">tag</span> <span class="cm-operator">==</span> <span class="cm-string">"dddd"</span>) <span class="cm-keyword">return</span> <span class="cm-variable">days</span>[<span class="cm-variable-2">date</span>.<span class="cm-property">getDay</span>()];
});
};</pre>
<p><a class="p_ident" id="p_B372u36cp6" href="#p_B372u36cp6" tabindex="-1" role="presentation"></a>رابط <code>ordinal</code> یک تابع ساده است در حالیکه <bdo><code>date-names</code></bdo> یک شیء را که حاوی چند چیز متفاوت است صادر میکند – دو مقداری که در نامهای آرایهها استفاده کردیم. استفاده از روش تجزیه (Destructuring) برای ایجاد متغیر برای رابطهای وارد شده بسیار مناسب است.</p>
<p>ماژول تابع رابط خودش را به <code>exports</code> اضافه میکند در نتیجه ماژولهای وابسته، به آن دسترسی خواهند داشت. میتوانیم از این ماژول به این صورت استفاده کنیم:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_pWURcHuHTt" href="#c_pWURcHuHTt" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> {<span class="cm-def">formatDate</span>} <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"./format-date"</span>);
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">formatDate</span>(<span class="cm-keyword">new</span> <span class="cm-variable">Date</span>(<span class="cm-number">2017</span>, <span class="cm-number">9</span>, <span class="cm-number">13</span>),
<span class="cm-string">"dddd the Do"</span>));
<span class="cm-comment">// → Friday the 13th</span></pre>
<p id="require">میتوان <code>require</code> را در کوتاهترین شکل خودش تعریف کرد:</p>
<pre class="snippet cm-s-default" data-language="javascript" data-sandbox="require"><a class="c_ident" id="c_CSMfqoYOzp" href="#c_CSMfqoYOzp" tabindex="-1" role="presentation"></a><span class="cm-variable">require</span>.<span class="cm-property">cache</span> <span class="cm-operator">=</span> <span class="cm-variable">Object</span>.<span class="cm-property">create</span>(<span class="cm-atom">null</span>);
<span class="cm-keyword">function</span> <span class="cm-def">require</span>(<span class="cm-def">name</span>) {
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span>(<span class="cm-variable-2">name</span> <span class="cm-keyword">in</span> <span class="cm-variable">require</span>.<span class="cm-property">cache</span>)) {
<span class="cm-keyword">let</span> <span class="cm-def">code</span> <span class="cm-operator">=</span> <span class="cm-variable">readFile</span>(<span class="cm-variable-2">name</span>);
<span class="cm-keyword">let</span> <span class="cm-def">module</span> <span class="cm-operator">=</span> {<span class="cm-property">exports</span>: {}};
<span class="cm-variable">require</span>.<span class="cm-property">cache</span>[<span class="cm-variable-2">name</span>] <span class="cm-operator">=</span> <span class="cm-variable-2">module</span>;
<span class="cm-keyword">let</span> <span class="cm-def">wrapper</span> <span class="cm-operator">=</span> <span class="cm-variable">Function</span>(<span class="cm-string">"require, exports, module"</span>, <span class="cm-variable-2">code</span>);
<span class="cm-variable-2">wrapper</span>(<span class="cm-variable">require</span>, <span class="cm-variable-2">module</span>.<span class="cm-property">exports</span>, <span class="cm-variable-2">module</span>);
}
<span class="cm-keyword">return</span> <span class="cm-variable">require</span>.<span class="cm-property">cache</span>[<span class="cm-variable-2">name</span>].<span class="cm-property">exports</span>;
}</pre>
<p><a class="p_ident" id="p_Kzh6PibMf8" href="#p_Kzh6PibMf8" tabindex="-1" role="presentation"></a>در این کد، <code>readFile</code> یک تابع ساختگی است که یک فایل را خوانده و محتوای آن را به صورت رشته برمیگرداند. جاوااسکریپت استاندارد، این قابلیت را فراهم نمیکند – اما محیطهای متفاوت جاوااسکریپت مثل مرورگر و <bdo>Node.js</bdo>، راههای خودشان را برای دسترسی به فایلها فراهم میسازند. مثال بالا فقط وانمود میکند که <code>readFile</code> وجود دارد.</p>
<p>برای جلوگیری از بارگیری چندبارهی یک ماژول، <code>require</code> ماژولهایی که تاکنون بارگیری شدهاند را جایی (در حافظهی نهان) نگه داری میکند. در زمان فراخوانی، ابتدا بارگیری ماژول خواسته شده را در گذشته بررسی میکند و در صورت نبود، آن را بارگیری میکند. این روند شامل خواندن کد ماژول، قرار دادن آن درون یک تابع و فراخوانی آن میشود.</p>
<p>رابط بستهی <code>ordinal</code> که قبلتر دیدیم یک شیء نیست بلکه یک تابع است. یک ایراد وارده به ماژولهای CommonJS این است که اگرچه سیستم ماژول، یک رابط به صورت شیئی خالی برای شما ایجاد میکند (که در <code>exports</code> قرار میگیرد)، میتوانید آن را با هر مقداری که بخواهید به وسیلهی بازنویسی خاصیت <bdo><code>module.exports</code></bdo> جایگزین کنید. این کار را خیلی از ماژولها انجام میدهند تا بتوانند یک مقدار ساده را به جای یک شیء رابط صادر کنند.</p>
<p><a class="p_ident" id="p_paQGxmzS1V" href="#p_paQGxmzS1V" tabindex="-1" role="presentation"></a>با تعریف <code>require</code>، <code>exports</code> و <code>module</code> به عنوان پارامترهای تابع wrapper تولید شده (و ارسال مقدارهای مناسب در هنگام فراخوانی)، بارگیرنده اطمینان حاصل میکند که این متغیرها در قلمروی مربوط به ماژول در دسترس خواهند بود.</p>
<p>روشی که در آن، رشتهی داده شده به <code>require</code> به یک نام فایل واقعی یا یک آدرس وب تفسیر میشود، در سیستمهای مختلف متفاوت است. زمانی که این رشته با <bdo><code>"./"</code></bdo> یا <bdo><code>"../"</code></bdo> شروع میشود عموما نسبت به نام فایل ماژول فعلی در نظر گرفته میشود. بنابراین <bdo><code>"./<wbr>format-date"</code></bdo> فایلی به نام <bdo><code>format-date.js</code></bdo> که در همان پوشه قرار دارد در نظر گرفته میشود.</p>
<p><a class="p_ident" id="p_XY1RLgBv7D" href="#p_XY1RLgBv7D" tabindex="-1" role="presentation"></a>زمانی که نام نسبی نیست، <bdo>Node.js</bdo> به جستجوی بستهای با همان نام اقدام میکند. در کد مثال این فصل، ما این گونه نامها را به عنوان بستههای NPM تفسیر میکنیم. در <a href="20_node.html">فصل 20</a> به جزئیات نصب و استفاده از ماژولهای NPM خواهیم پرداخت.</p>
<p id="modules_ini"><a class="p_ident" id="p_uDGpTokBLh" href="#p_uDGpTokBLh" tabindex="-1" role="presentation"></a>اکنون به جای نوشتن تجزیهگر فایل INI خودمان، میتوانیم از یکی از بستههای موجود در NPM استفاده کنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_LfcCXOMZGr" href="#c_LfcCXOMZGr" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> {<span class="cm-def">parse</span>} <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"ini"</span>);
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">parse</span>(<span class="cm-string">"x = 10\ny = 20"</span>));
<span class="cm-comment">// → {x: "10", y: "20"}</span></pre>
<h2><a class="h_ident" id="h_9mUT8reb1B" href="#h_9mUT8reb1B" tabindex="-1" role="presentation"></a>ماژولهای ECMASCRIPT</h2>
<p><a class="p_ident" id="p_TQD7q+ypOS" href="#p_TQD7q+ypOS" tabindex="-1" role="presentation"></a>ماژولهای CommonJS به خوبی کار میکنند و ترکیب آنها با NPM به جامعهی برنامهنویسان جاوااسکریپت اجازه داده است که کدها را در مقیاس بزرگ به اشتراک بگذارند.</p>
<p>اما کمی لازم است تا دستی به سر و روی آنها بکشیم. ظاهر نوشتاری آن اندکی مشکل دارد – به عنوان مثال چیزهایی را که به <code>exports</code> اضافه میکنید در قلمروی محلی در دسترس نیستند. و به دلیل اینکه <code>require</code> یک فراخوانی به یک تابع معمولی است که هر نوع آرگومانی را قبول میکند، نه فقط مقادیر رشتهای، تشخیص وابستگیهای یک ماژول بدون اجرای کدهای آن میتواند سخت شود.</p>
<p id="es"><a class="p_ident" id="p_0cKp9Xub+L" href="#p_0cKp9Xub+L" tabindex="-1" role="presentation"></a>به همین علت است که جاوااسکریپت استاندارد از نسخهی 2015، سیستم ماژول متفاوت خودش را معرفی کرد که معمولا <em>ES modules</em> نامیده میشود. <em>ES</em> مخفف <em>ECMAScript</em> است. مفاهیم اصلی وابستگیها و رابطها به همان صورت باقی میماند اما جزئیات آنها متفاوت است. درنتیجه، نشانهگذاری آن اکنون درون زبان یکپارچه شده است. به جای فراخوانی یک تابع برای دسترسی به یک وابستگی، از کلیدواژهی مخصوصی به نام <code>import</code> استفاده میکنید.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_EpiH8qOAcJ" href="#c_EpiH8qOAcJ" tabindex="-1" role="presentation"></a><span class="cm-keyword">import</span> <span class="cm-def">ordinal</span> <span class="cm-keyword">from</span> <span class="cm-string">"ordinal"</span>;
<span class="cm-keyword">import</span> {<span class="cm-def">days</span>, <span class="cm-def">months</span>} <span class="cm-keyword">from</span> <span class="cm-string">"date-names"</span>;
<span class="cm-keyword">export</span> <span class="cm-keyword">function</span> <span class="cm-def">formatDate</span>(<span class="cm-def">date</span>, <span class="cm-def">format</span>) { <span class="cm-comment">/* ... */</span> }</pre>
<p>به طور مشابه، کلیدواژهی <code>export</code> برای صدور استفاده میشود. میتوان از آن در ابتدای یک تابع، کلاس، یا تعریف متغیر استفاده کرد (<code>let</code>، <code>const</code>، یا <code>var</code>)</p>
<p><a class="p_ident" id="p_neuruqAaGQ" href="#p_neuruqAaGQ" tabindex="-1" role="presentation"></a>یک رابط ماژول ES، یک مقدار واحد نیست بلکه مجموعهای از متغیرهای نامگذاری شده است. ماژولی که در بالا آمده است <code>formatDate</code> را به یک تابع تخصیص میدهد. زمانی که از یک ماژول دیگر اقدام به <em>وارد کردن</em> میکنید شما متغیر را وارد میکنید نه مقدار آن را که معنای آن این است که یک صدور ماژول ممکن است مقدار یک متغیر را در هر زمانی تغییر دهد و ماژولهایی که آن را وارد کردهاند، مقدار جدیدش را خواهند دید.</p>
<p><a class="p_ident" id="p_dQXWSlTgYb" href="#p_dQXWSlTgYb" tabindex="-1" role="presentation"></a>زمانی که انتسابی را به صورت <code>default</code> مشخص میکنید، آن انتساب یا متغیر به عنوان مقدار صادر شدهی اصلی ماژول در نظر گرفته میشود. اگر ماژولی مانند <code>ordinal</code> را که در مثال آمد، بدون استفاده از کروشههای دور نام متغیر، <code>import</code> کنید، متغیر default آن ماژول را دریافت میکنید. این گونه ماژولها میتوانند در کنار صدور <code>default</code>، متغیرهای دیگری را هم با نامهای مختلف صادر کنند.</p>
<p>برای ایجاد یک صدور پیش فرض (default) میتوانید از <bdo><code>export default</code></bdo> قبل از یک عبارت، تعریف تابع یا کلاس استفاده کنید.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_Y6Wnu9X+/W" href="#c_Y6Wnu9X+/W" tabindex="-1" role="presentation"></a><span class="cm-keyword">export</span> <span class="cm-keyword">default</span> [<span class="cm-string">"Winter"</span>, <span class="cm-string">"Spring"</span>, <span class="cm-string">"Summer"</span>, <span class="cm-string">"Autumn"</span>];</pre>
<p>میتوان نام متغیرهایی که وارد شدهاند را با استفاده از <code>as</code> تغییر داد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_I7jPaQvsXj" href="#c_I7jPaQvsXj" tabindex="-1" role="presentation"></a><span class="cm-keyword">import</span> {<span class="cm-def">days</span> <span class="cm-keyword">as</span> <span class="cm-def">dayNames</span>} <span class="cm-keyword">from</span> <span class="cm-string">"date-names"</span>;
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">dayNames</span>.<span class="cm-property">length</span>);
<span class="cm-comment">// → 7</span></pre>
<p><a class="p_ident" id="p_bRZWqopREO" href="#p_bRZWqopREO" tabindex="-1" role="presentation"></a>یک تفاوت مهم دیگر این است که عمل import ماژول ES قبل از این که اجرای اسکریپت ماژول شروع شود اتفاق میافتد. یعنی import را نمیتوان درون تابع یا بلاکها قرار داد و نام وابستگیها باید به صورت رشته محصور در نقل قول باشد نه عبارتهای قابل ارزیابی جاوااسکریپت.</p>
<p><a class="p_ident" id="p_JO4Dywzaop" href="#p_JO4Dywzaop" tabindex="-1" role="presentation"></a>در زمان نوشتن این کتاب، جامعهی برنامهنویسان جاوااسکریپت در مسیر استفاده از این سبک ماژولها هستند. اما این روند به آهستگی اتفاق میافتد. چند سالی بعد از مشخص شدن فرمت جدید طول کشید تا مرورگرها و Node.js پشتیبانی از آن را شروع کنند. و اگرچه آنها تقریبا از آن پشتیبانی کامل میکنند اما این پشتیبانی هنوز با ایراداتی روبرو است و بحثهایی پیرامون شیوهی توزیع این گونه ماژولها در NPM هنوز در جریان است.</p>
<p>خیلی از برنامهها را با ماژولهای ES مینویسند و به صورت خودکار به دیگر فرمتها در هنگام انتشار تبدیل میکنند. ما در دورهی گذار به سر میبریم که در آن دو سیستم مدیریت ماژول همزمان استفاده میشوند و خوب است که قادر باشیم کدها را در هر دو سیستم بنویسیم و بخوانیم.</p>
<h2><a class="h_ident" id="h_hODOUYQ6ls" href="#h_hODOUYQ6ls" tabindex="-1" role="presentation"></a>ساخت و بستهبندی</h2>
<p>در واقع خیلی از پروژههای جاوااسکریپت، از لحاظ فنی، در خود زبان جاوااسکریپت نوشته نمیشوند. افزونههایی وجود دارند، مانند همان گویشی از جاوااسکریپت که به انواع داده حساس بود و در <a href="08_error.html#typing">فصل 8</a> ذکر شد، که بسیار محبوب میباشند. برنامه نویسان اغلب خیلی زودتر به سراغ استفاده از افزونههای برنامهریزی شده برای زبان میروند قبل از آنکه به محیطهای مجری جاوااسکریپت اضافه شوند.</p>
<p>ببرای اجرایی کردن این کار، کدهایشان را کامپایل میکنند، کد را از گویش جاوااسکریپت مورد نظرشان به جاوااسکریپت ساده ترجمه میکنند – یا حتی به یک نسخهی قدیمی از جاوااسکریپت ترجمه میکنند که مرورگرهای قدیمی بتوانند آن را اجرا کنند.</p>
<p><a class="p_ident" id="p_GgZhex5Gzo" href="#p_GgZhex5Gzo" tabindex="-1" role="presentation"></a>قرار دادن یک برنامهی ماژولار که از 200 فایل متفاوت تشکیل شده است در یک صفحهی وب مشکلات خودش را خواهد داشت. اگر دریافت و استفاده از یک فایل در شبکه 50 هزارم ثانیه زمان بگیرد، کل برنامه ده ثانیه زمان خواهد گرفت یا شاید نیمی از آن زمان اگر بتوانیم چندین فایل را همزمان بارگیری کنیم. این زمان زیادی را تلف خواهد کرد. به دلیل اینکه بارگیری یک فایل بزرگ واحد، از تعداد زیادی فایل کوچک، عموما سریعتر اتفاق میافتد. برنامهنویسان وب به سراغ ابزارهایی رفتهاند که برنامههایشان را قبل از اینکه بخواهند در وب منتشر کنند (که با زحمت به ماژولها تقسیم کردهاند) گرفته و تبدیل به یک فایل بزرگ کند. این ابزار را بستهساز مینامند (bundlers).</p>
<p><a class="p_ident" id="p_WchYnejdba" href="#p_WchYnejdba" tabindex="-1" role="presentation"></a>میتوانیم پا را فراتر بگذاریم. جدا از تعداد فایلها، حجم این فایلها هم در سرعت انتقالشان در شبکه تعیینکننده است. بنابراین، جامعهی برنامهنویسان جاوااسکریپت، ابزارهای فشرده ساز را اختراع کردند (minifier). این ابزارها یک برنامهی جاوااسکریپت را گرفته و با حذف توضیحات، فضاهای خالی، تغییر نام متغیرها و جایگزینی بعضی کدها با معادلهای کوچکتر، حجم آن را کاهش میدهند.</p>
<p><a class="p_ident" id="p_UAhsGLYB2Z" href="#p_UAhsGLYB2Z" tabindex="-1" role="presentation"></a>پس اصلا دور از انتظار نیست که کدی که در یک بستهی NPM یافتهاید یا در یک صفحهی وب اجرا میشود پیش از آن مراحل مختلفی از تبدیل را طی کرده باشد – تبدیل از جاوااسکریپت مدرن به جاوااسکریپت قدیمیتر، از ماژولهای ES به CommonJS، بستهبندی و فشرده شده باشد. در این کتاب به جزئیات مربوط به این ابزار نمیپردازیم، چون هم کسلکننده خواهد بود هم به سرعت تغییر میکنند. فقط حواستان باشد که کدی که شما معمولا اجرا میکنید اغلب همانی نیست که نوشته شده است.</p>
<h2><a class="h_ident" id="h_w+msYeXVHp" href="#h_w+msYeXVHp" tabindex="-1" role="presentation"></a>طراحی ماژول</h2>
<p>ساختاردهی به یک برنامه یکی از جنبههای حساس و ظریف از برنامهنویسی محسوب میشود. هر قطعهی معناداری از قابلیتها را میتوان به اشکال مختلفی مدلسازی کرد.</p>
<p>طراحی یک برنامهی خوب، سلیقهای است – مصالحههایی پیش میآید که سلیقه در آن دخیل است. بهترین راه برای یادگیری ارزش یک طراحی دارای ساختار خوب، این است که برنامههای زیادی را مورد مطالعه قرار دهید یا با آنها کار کنید تا متوجه شوید که چه چیزی مفید و چه چیز نادرست است. تصور نکنید که درهمریختگی و آشفتگی به هر حال پیش خواهد آمد. تقریبا هر چیزی را با صرف کمی تفکر بیشتر میتوان با سازماندهی بهتر کرد.</p>
<p>یکی از جنبههای طراحی ماژول که باید رعایت شود، آسانی استفاده است. اگر چیزی را میسازید که قرار است توسط کاربران متعددی استفاده شود (یا حتی توسط خودتان، طی مدت سه ماه، زمانی که دیگر جزئیات کاری که انجام دادهاید را به خاطر نمیآورید)، اگر رابط برنامهی شما ساده و قابل تشخیص باشد استفاده از آن را راحتتر میکند.</p>
<p><a class="p_ident" id="p_5+sfP6l0gN" href="#p_5+sfP6l0gN" tabindex="-1" role="presentation"></a>ممکن است معنای آن این باشد که از قراردادهای موجود پیروی کنید. یک مثال خوب میتواند بستهی <code>ini</code> باشد. این ماژول، کار بستهی استاندارد <code>JSON</code> را با فراهم نمودن <code>parse</code> و <code>stringify</code> (برای نوشتن یک فایل INI) تقلید میکند و شبیه به <code>JSON</code> عمل تبدیل بین رشتهها و اشیاء را انجام میدهد. بنابراین رابط در اینجا کوچک و آشنا است و بعد از اینکه برای اولین بار از آن استفاده کنید، احتمالا روش استفاده از آن را به خاطر خواهید داشت.</p>
<p><a class="p_ident" id="p_I5scgYLK4v" href="#p_I5scgYLK4v" tabindex="-1" role="presentation"></a>حتی اگر هیچ تابع استاندارد یا بستهای که به طور گسترده استفاده میشود برای تقلید وجود نداشت، میتوانید با استفاده از ساختارهای دادهی ساده و تمرکز بر انجام یک کار، ماژولهای خودتان را قابل پیشبینی کنید. به عنوان مثال، خیلی از بستههای تجزیهی فایل INI در NPM تابعی دارند که مستقیما یک فایل ini را از دیسک سخت خوانده و تجزیه میکند. این کار باعث میشود که نتوان از این ماژولها در مرورگر استفاده کرد، به دلیل اینکه در مرورگر به طور مستقیم به سیستم فایل دسترسی نداریم و پیچیدگی ای اضافه میکند که بهتر بود با ترکیب ماژول با یک تابع خواندن فایل دیگر، حل میشد.</p>
<p>این موضوع به یکی دیگر از جنبههای مفیدی که در طراحی ماژول وجود دارد اشاره میکند – آسانی ترکیب با کدهای دیگر. ماژولهایی که تمرکز روی کار خاصی دارند و مقدارها را محاسبه میکنند در طیف گستردهتری از برنامهها کاربرد دارند نسبت به ماژولهای بزرگتری که کارهای پیچیدهای انجام میدهند و دارای اثرات جانبی هستند. خوانندهی فایل INIای که اصرار به خواندن فایل از دیسک سخت را دارد در مواردی که محتوای فایل از دیگر منابع میآید کاربردی نخواهد داشت.</p>
<p><a class="p_ident" id="p_btxtSvBXyC" href="#p_btxtSvBXyC" tabindex="-1" role="presentation"></a>همچنین، اشیاء دارای وضعیت (stateful) گاهی اوقات مفید هستند یا حتی لازماند اما اگر چیزی را بتوان با یک تابع انجام داد، از تابع استفاده کنید. تعدادی از بستههای خوانندهی فایل INI در NPM، سبکی را برای رابط خود استفاده کردهاند که ابتدا از شما میخواهند که یک شیء ایجاد کنید، بعد فایل مورد نظر را درون شیء بارگیری کنید و در نهایت از متدهای خاصی برای گرفتن نتایج استفاده کنید. این کار در سنت شیء گرایی رایج است و باید گفت بسیار بد است. به جای ساختن یک فراخوانی تابع و ادامه حرکت، باید تشریفات حرکت شیءتان بین وضعیتهای مختلف را انجام دهید. و به دلیل اینکه دادهها اکنون در یک نوع خاصی از شیء قرار گرفتهاند، تمام کدی که با آن در ارتباط است باید آن نوع را بداند که باعث ایجاد وابستگیهای درونی بیفایده میشود.</p>
<p>اغلب نمیتوان از تعریف ساختارهای دادهی جدید صرف نظر کرد – فقط تعداد کمی از موارد اساسی و پایهای توسط زبان استاندارد فراهم شده است و خیلی از انواع داده میبایست پیچیدهتر از یک آرایه یا یک نگاشت باشد. اما زمانی که یک آرایه کافی است، از همان استفاده کنید.</p>
<p>یک نمونه از ساختار دادههای کمی پیچیدهتر، گراف است که در <a href="07_robot.html">فصل 7</a> ذکر شد. یک راه یکسان و مشخص برای نمایش یک گراف در جاوااسکریپت وجود ندارد. در آن فصل، ما از یک شیء که خاصیتهایش آرایههایی از رشتهها را نگه داری میکردند استفاده کردیم –گرههایی که از یکگره قابل دستیابی بودند.</p>
<p>بستههای متعددی دربارهی مسیریابی در NPM وجود دارند اما هیچ کدامشان از این فرمت گراف استفاده نمیکنند. معمولا این بستهها به یالهای گراف امکان داشتن وزن را میدهند، که همان فاصله یا هزینهای است که به آنها اختصاص داده شده است. در نمایش ما اینکار ممکن نیست.</p>
<p>به عنوان مثال، بستهای به نام <code>dijkstrajs</code> وجود دارد. یک راه حل شناخته شده برای مسیریابی، که نسبتا شبیه به تابع <code>findRoute</code> ما است، <em>الگوریتم دیکسترا</em> است که ادسخر دیکسترا آن را نوشت. پسوند <code>js</code> معمولا به انتهای بستهها اضافه میشود تا نشان دهد که آنها به زبان جاوااسکریپت نوشته شدهاند. بستهی <code>dijkstrajs</code> از یک فرمت گراف شبیه به فرمت ما استفاده میکند اما به جای آرایهها از اشیاء استفاده میکند که مقدارهای خاصیتهایش از جنس اعداد هستند – وزن یالها.</p>
<p>بنابراین اگر بخواهیم از آن بسته استفاده کنیم، باید اطمینان حاصل کنیم که گراف ما در فرمتی که بسته پشتیبانی میکند ذخیره شده باشد. همهی یالها وزن یکسانی میگیرند زیرا مدل سادهشدهی ما برای هر مسیر هزینهی یکسانی در نظر میگیرد (یک دور).</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_NyRXVpwPYN" href="#c_NyRXVpwPYN" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> {<span class="cm-def">find_path</span>} <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"dijkstrajs"</span>);
<span class="cm-keyword">let</span> <span class="cm-def">graph</span> <span class="cm-operator">=</span> {};
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">node</span> <span class="cm-keyword">of</span> <span class="cm-variable">Object</span>.<span class="cm-property">keys</span>(<span class="cm-variable">roadGraph</span>)) {
<span class="cm-keyword">let</span> <span class="cm-def">edges</span> <span class="cm-operator">=</span> <span class="cm-variable">graph</span>[<span class="cm-variable">node</span>] <span class="cm-operator">=</span> {};
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">dest</span> <span class="cm-keyword">of</span> <span class="cm-variable">roadGraph</span>[<span class="cm-variable">node</span>]) {
<span class="cm-variable-2">edges</span>[<span class="cm-variable-2">dest</span>] <span class="cm-operator">=</span> <span class="cm-number">1</span>;
}
}
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-variable">find_path</span>(<span class="cm-variable">graph</span>, <span class="cm-string">"Post Office"</span>, <span class="cm-string">"Cabin"</span>));
<span class="cm-comment">// → ["Post Office", "Alice's House", "Cabin"]</span></pre>
<p>این خود مانعی برای ترکیبپذیری محسوب میشود – زمانی که بستههای متنوع از ساختارهای متفاوتی برای توصیف چیزهای یکسان استفاده میکنند، ترکیب آنها سخت خواهد شد. بنابراین، اگر قصد دارید ترکیبپذیری را هم در طراحی لحاظ کنید، توجه کنید که چه ساختارهای دادهای دیگر برنامهنویسان استفاده میکنند و هر وقت ممکن شد از روش آنها استفاده کنید.</p>
<h2><a class="h_ident" id="h_EzvDUHyjs2" href="#h_EzvDUHyjs2" tabindex="-1" role="presentation"></a>خلاصه</h2>
<p>ماژولها برای برنامههای بزرگتر، ساختار ایجاد میکنند و این کار را با جداسازی کدها به بخشهایی با رابطها و وابستگیهای شفاف انجام میدهند. رابط بخشی از ماژول است که برای دیگر ماژولها، قابل رویت است و وابستگیها، ماژولهای دیگری هستند که ماژول از آنها استفاده میکند.</p>
<p><a class="p_ident" id="p_TpamJQ0QM/" href="#p_TpamJQ0QM/" tabindex="-1" role="presentation"></a>به دلیل اینکه جاوااسکریپت از ابتدا سیستم ماژول نداشت، سیستم CommonJS با استفاده از خود زبان ایجاد شد. اما بعد از مدتی، جاوااسکریپت سیستم داخلی خودش را ایجاد کرد؛ سیستمی که در کنار سیستم CommonJS وجود دارد.</p>
<p>یک بسته، قطعه کدی است که میتوان آن را مستقلا توزیع کرد. NPM مخزن بستههای جاوااسکریپت است. میتوانید انواع بستههای کاربردی (و بدون کاربرد) را از آن دانلود کنید.</p>
<h2><a class="h_ident" id="h_ggOFdVwDCk" href="#h_ggOFdVwDCk" tabindex="-1" role="presentation"></a>تمرینها</h2>
<h3><a class="i_ident" id="i_PNOpAg8YNg" href="#i_PNOpAg8YNg" tabindex="-1" role="presentation"></a>یک ربات ماژولار</h3>
<p id="modular_robot">موارد زیر، متغیرهایی هستند که پروژهی <a href="07_robot.html">فصل 7</a> ایجاد میکند:</p>
<pre class="snippet cm-s-default" data-language="text/plain" ><a class="c_ident" id="c_/nxTd1W0Sy" href="#c_/nxTd1W0Sy" tabindex="-1" role="presentation"></a>roads
buildGraph
roadGraph
VillageState
runRobot
randomPick
randomRobot
mailRoute
routeRobot
findRoute
goalOrientedRobot</pre>
<p>اگر بخواهید آن پروژه را به صورت برنامهای ماژولار بنویسید، چه ماژولهایی ایجاد میکنید. کدام ماژول به یک ماژول دیگر وابستگی خواهد داشد؟ و رابط آنها چگونه خواهد بود؟</p>
<p><a class="p_ident" id="p_HfYoGdoaSL" href="#p_HfYoGdoaSL" tabindex="-1" role="presentation"></a>کدام قسمتها احتمالا قبلا نوشته شدهاند و از NPM در دسترس هستند؟ ترجیح میدهید تا از بستههای NPM استفاده کنید یا خودتان آنها را بنویسید؟</p>
<div class="solution"><div class="solution-text">
<p>من اگر بودم این کار را میکردم (البته فقط یک راه درست برای طراحی یک ماژول وجود ندارد):</p>
<p>کدی که برای ساخت گراف مسیر استفاده میشود در درون ماژول <code>graph</code> وجود دارد. به دلیل اینکه من ترجیح میدهم از بستهی <code>dijkstrajs</code> از NPM استفاده کنم تا اینکه خودم بنویسم، گراف را طوری خواهیم ساخت که دادههایی مناسب بستهی <code>dijkstajs</code> تولید کند. این ماژول یک تابع صدور میکند، تابع <code>buildGraph</code>. من تابع <code>buildGraph</code> را طوری میسازم که آرایهای از آرایههای دو عنصری را به جای رشتههایی که خط تیره دارند بپذیرد تا ماژول کمتر به فرمت ورودی وابسته باشد.</p>
<p>ماژول <code>roads</code> حاوی دادههای خام مربوط به راهها (آرایهی <code>roads</code>) به همراه متغیر <code>roadGraph</code> میباشد. این ماژول به <bdo><code>./graph</code></bdo> وابستگی دارد و گراف راه را صادر میکند.</p>
<p><a class="p_ident" id="p_zeuEX/V35c" href="#p_zeuEX/V35c" tabindex="-1" role="presentation"></a>کلاس <code>VillageState</code> در ماژول <code>state</code> قرار دارد. این کلاس به ماژول <bdo><code>./roads</code></bdo> وابستگی دارد به این خاطر که نیاز دارد تا وجود راه داده شده را اعتبارسنجی کند. همچنین به <code>randomPick</code> نیاز دارد. چون این تابع سه خط کد بیشتر ندارد، میتوانیم آن را درون ماژول <code>state</code> به عنوان یک تابع کمکی قرار دهیم. اما <code>randomRobot</code> هم به آن نیاز دارد. پس باید یا کد را تکرار کنیم یا یک ماژول برایش در نظر بگیریم. این تابع در NPM به نام <code>random-item</code> وجود دارد پس بهتر است که هر دو ماژول را به آن وابسته کنیم. میتوانیم تابع runRobot را به این ماژول نیز اضافه کنیم چراکه هم کوچک است و هم به مدیریت وضعیت مرتبط میباشد. ماژول هر دوی کلاس <code>VillageState</code> و تابع <code>runRobot</code> را صادر مینماید.</p>
<p>در انتها، رباتها به همراه مقادیری که به آنها وابسته اند مانند <code>mailRoute</code> را میتوان درون یک ماژول <code>example-robots</code> قرار داد که خود این ماژول وابسته به <bdo><code>./roads</code></bdo> میباشد و تابع <code>robot</code> را صادر میکند. برای اینکه امکان مسیریابی را برای <code>goalOrientedRobot</code> فراهم کنیم، این ماژول همچنین به <code>dijkstrajs</code> وابسته خواهد بود.</p>
<p>با سپردن بخشی از کارها به ماژولهای NPM، کد ما اندکی کوچکتر میشود. هر ماژول مجزا کاری نسبتا ساده انجام میدهد و مستقل عمل میکند. تقسیم کد به ماژولها اغلب موجب بهبودهای بیشتری در طراحی برنامه میشود. در این مثال، کمی غیر طبیعی به نظر میرسد که <code>VillageState</code> و رباتها به یک گراف مسیر خاص وابسته هستند. احتمالا بهتر است که گراف را به عنوان یک آرگومان برای سازندهی وضعیت استفاده کنیم و رباتها آن را از شیء وضعیت بخوانند - که این کار وابستگیها را کاهش میدهد (که همیشه خوب است) و امکان اجرای شبیهسازی روی نقشههای متفاوت را هم فراهم میکند (که عالی است).</p>
<p>آیا ایدهی خوبی است که از ماژولهای NPM به جای چیزهایی که خودمان میتوانستیم بنویسیم استفاده کنیم؟ قاعدتا بله. برای موارد مهم مثل همین مسیریابی که در صورت کدنویسی توسط خودتان، احتمال اشتباه و اتلاف وقت، زیاد است. برای تابعهای کوچک مثل <code>random-item</code>، که نوشتن آن خیلی ساده است، استفاده از آنها در جاهای مختلف میتواند ماژول شما را شلوغ و بینظم کند.</p>
<p>به هر حال، نباید کاری که صرف پیدا کردن یک بستهی مناسب در NPM میشود را هم دست کم بگیرید. و حتی اگر بستهی مورد نظر را پیدا کردید، ممکن است به خوبی کار نکند یا ویژگیهای مورد نظر شما را نداشته باشد. علاوه بر آن، وابسته بودن به بستههای NPM به این معنا است که باید اطمینان حاصل کنید که آنها نصب شدهاند، و باید به همراه برنامهتان توزیع شوند، و هر از چند گاهی باید به روزرسانی شوند.</p>
<p>بنابراین یک بار دیگر، این تصمیم نوعی مصالحه و بده بستان است و شما باید میزان کمکی که این بستهها به برنامه شما میکنند را در نظر بگیرید.</p>
</div></div>
<h3><a class="i_ident" id="i_1lUdCYxQ0G" href="#i_1lUdCYxQ0G" tabindex="-1" role="presentation"></a>ماژول راهها</h3>
<p>ماژول CommonJS ای بنویسید که بر اساس مثال <a href="07_robot.html">فصل 7</a> آرایهای از راهها را داشته باشد و ساختار دادهی گراف را به عنوان <code>roadGraph</code> صادر کند. باید به ماژولی به نام <bdo><code>./graph</code></bdo> وابسته باشد که تابعی به نام <code>buildGraph</code> را صادر میکند که برای ساخت گراف استفاده میشود. این تابع نیاز به آرایهای از آرایههای دو عنصری دارد (نقاط شروع و پایان راهها).</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_FPEwj7xDOs" href="#c_FPEwj7xDOs" tabindex="-1" role="presentation"></a><span class="cm-comment">// Add dependencies and exports</span>
<span class="cm-keyword">const</span> <span class="cm-def">roads</span> <span class="cm-operator">=</span> [
<span class="cm-string">"Alice's House-Bob's House"</span>, <span class="cm-string">"Alice's House-Cabin"</span>,
<span class="cm-string">"Alice's House-Post Office"</span>, <span class="cm-string">"Bob's House-Town Hall"</span>,
<span class="cm-string">"Daria's House-Ernie's House"</span>, <span class="cm-string">"Daria's House-Town Hall"</span>,
<span class="cm-string">"Ernie's House-Grete's House"</span>, <span class="cm-string">"Grete's House-Farm"</span>,
<span class="cm-string">"Grete's House-Shop"</span>, <span class="cm-string">"Marketplace-Farm"</span>,
<span class="cm-string">"Marketplace-Post Office"</span>, <span class="cm-string">"Marketplace-Shop"</span>,
<span class="cm-string">"Marketplace-Town Hall"</span>, <span class="cm-string">"Shop-Town Hall"</span>
];</pre>
<div class="solution"><div class="solution-text">
<p><a class="p_ident" id="p_oepUYccAFO" href="#p_oepUYccAFO" tabindex="-1" role="presentation"></a>چون این یک ماژول <code>CommonJS</code> است، باید از <code>require</code> برای ورود ماژول گراف استفاده کنید. این ماژول به صورت تابع <code>buildGraph</code> مشخص شده است که میتوانید آن را از رابط شیءاش به وسیله اعلان <code>const</code> و روش تجزیه (destruction) مورد ارجاع قرار دهید.</p>
<p>برای صدور <code>roadGraph</code>، یک خاصیت به شیء <code>exports</code> اضافه میکنید. به دلیل اینکه <code>buildGraph</code> ساختار دادهای دریافت میکند که دقیقا با <code>roads</code> مطابقت ندارد، عمل جداسازی رشتهی راهها باید در درون ماژول شما انجام شود.</p>
</div></div>
<h3><a class="i_ident" id="i_iDVww/K6AG" href="#i_iDVww/K6AG" tabindex="-1" role="presentation"></a>وابستگیهای دایرهای</h3>
<p><a class="p_ident" id="p_HsCfDi9xUv" href="#p_HsCfDi9xUv" tabindex="-1" role="presentation"></a>یک وابستگی دایرهای در شرایطی رخ میدهد که ماژول A به ماژول B و ماژول B نیز مستقیم یا غیر مستقیم، به A وابسته باشد. خیلی از سیستمهای ماژول، این کار را ممنوع کردهاند به دلیل اینکه هر ترتیبی که شما انتخاب کنید برای بارگیری این گونه ماژولها، نمیتوان قبل از اجرا مطمئن شد وابستگیهای هر ماژول بارگیری شدهاند یا خیر.</p>
<p><a class="p_ident" id="p_8+oiC535F6" href="#p_8+oiC535F6" tabindex="-1" role="presentation"></a>ماژولهای CommonJS شکل محدودی از وابستگیهای دایرهای را اجازه میدهد. تا زمانی که ماژولها، اشیاء export پیشفرضشان را جایگزین نکنند و به رابطهای یکدیگر اجازهی دسترسی قبل از پایان بارگیری ندهند، وابستگیهای دایرهای مجاز هستند.</p>
<p>تابع <code>require</code> که <a href="10_modules.html#require">پیشتر در این فصل</a> آمد، از این نوع از چرخهی وابستگی پشتیبانی میکند. آیا میتوانید توضیح دهید چگونه این کار را انجام میدهد؟ چه مشکلی پیش میآید اگر یک ماژول در یک چرخه، شیء <code>export</code> پیشفرض خود را جایگزین کند؟</p>
<div class="solution"><div class="solution-text">
<p>نکته این است که <code>require</code> ماژولها را پیش از آنکه شروع به بارگیری ماژول کند به حافظهی نهانش اضافه میکند. در این صورت، اگر در حین اجرای یک <code>require</code> فراخوانی دیگری به ماژول صورت گیرد، تابع از پیش نسبت به آن باخبر است و رابط فعلی برگردانده میشود و از بارگیری مجدد و اضافی ماژول جلوگیری میکند ( که این بارگیری مجدد باعث رخ دادن سرریز خواهد شد).</p>
<p>اگر یک ماژول مقدار <bdo><code>module.exports</code></bdo> خود را بازنویسی کند، هر ماژول دیگری که مقدار رابط آن ماژول را قبل از پایان بارگیریاش دریافت کرده است، شیء رابط پیشفرض را نگه خواهد داشت (که احتمالا تهی است) نه مقدار رابطی که انتظارش را دارد.</p>
</div></div><nav><a href="09_regexp.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a> <a href="11_async.html" title="next chapter">▶</a></nav>
</article>