انواع ماژول سیستم ها - module system در جاوا اسکریپت

انواع ماژول سیستم ها - module system در جاوا اسکریپت

پیش از این در مقاله‌ی module system در جاوااسکریپت چیست؟ درباره‌ی ماژول‌ها در جاوا اسکریپت صحبت کردیم. گفتیم که در جاوااسکریپت قطعه کدهای مستقل از هم را به ماژول‌های مجزایی تبدیل می‌کنیم تا راحت تر بتوانیم آنها را مدیریت، استفاده، ویرایش و به روز کنیم. در واقع به این ترتیب آنها را کپسوله می‌کنیم.

همچنین اشاره کردیم که ماژول‌ها در جاوااسکریپت مشابه کتابخانه‌های کوچک در سایر زبان‌های برنامه نویسی عمل می‌کنند (یا مانند کلاس‌ها در زبان‌های جاوا و پایتون) و کوچکترین واحد functionality به حساب می‌آیند. در واقع برنامه‌های بزرگ و پیچیده‌ی جاوااسکریپتی، از کنار هم قرار گرفتن ماژول‌های ریز و درشت ایجاد شده‌اند.

به اهمیت وجود ماژول‌ها و تاریخچه‌ی شکل گیری آنها نیز اشاره‌ای داشتیم. به زبان ساده ماژول یک فایل جاوا اسکریپت است که حداقل یکی از نماد (symbol) هایی را که تعریف می‌کند، به عنوان خروجی ارائه می‌کند (export می‌کند). خواه این نماد متغیر، تابع (function) یا شیء(object) باشد. قطعه کدهای مستقلی که قابلیت استفاده‌ی مجدد دارند و می‌توانیم آنها را به راحتی به کدمان اضافه و یا آنها را از کدمان حذف کنیم.

در آنجا به اختصار گفتیم که در گذشته و قبل از ظهور ماژول‌ها، دنیای جاوا اسکریپت چه مشکلات و محدودیت‌هایی داشت.

در انتها نیز به یک تعریف ساده از module system ها رسیدیم و گفتیم که module system (ماژول سیستم) در جاوا اسکریپت در واقع قواعدی‌ست که چگونگی رفتار و نوشتار ماژول‌ها و نحوه‌ی ارتباط آنها با یکدیگر را تعیین می‌کند. در واقع اینکه وقتی می‌خواهیم یک ماژول را به کدمان اضافه کنیم، باید از import استفاده کنیم یا از require یا define، به این بستگی دارد که جاوا اسکریپت را در چه محیطی و تحت چه module system ای اجرا می‌کنیم. به عبارت دیگر اینکه یک کد را چگونه به یک ماژول تبدیل کنیم و آن را پکیج کنیم، وابسته به این است که می‌خواهیم آن را در قالب چه module system ای خروجی دهیم و از آن در قالب چه module system ای استفاده کنیم.

امروزه ابزارهای CLI و تنظیمات آماده‌ای که برای module bundler ها وجود دارد، دردسرهای مربوط به module system ها و تبدیل آنها به یکدیگر را بسیار کم کرده است. اما با این حال، دانستن درباره‌ی آنها به ما کمک می‌کند که درک عمیق‌تری از رفتار کدهای مختلف داشته باشیم. به علاوه این کار باعث می‌شود که خطاهای کدمان را راحت تر پیدا کنیم و در مجموع یک قدم به سمت حرفه‌ای تر شدن حرکت کنیم.

برای محیط‌های مختلف و برای رفع مشکلات مختلفی که در ارتباط با ماژول‌ها وجود داشت، به مرور زمان module system هایی توسعه داده شدند که از جمله‌ی مهمترین آنها می‌توان به AMD ،CommonJS ،UMD و ES Modules اشاره کرد. ما در این مقاله درباره‌ی این module system ها بیشتر خواهیم گفت. شایان ذکر است که در بسیاری قسمت‌های این مقاله، از این مقاله ی مفید در سایت medium بهره برده‌ایم.

ماژول سیستم CommonJS

یکی از معروف‌ترین module system هایی که در اکوسیستم جاوااسکریپت وجود دارد، CommonJS است. حتماً شما هم تا به حال جایی آن را دیده‌اید یا در نوشته یا مقاله‌ای به این اسم برخورده‌اید. در واقع CommonJS همان استانداردی‌ست که طبق آن ماژول‌ها با استفاده از عبارت module.exports (یا exports به تنهایی) مقداری را که می‌خواهند خروجی می‌دهند و با استفاده از متد ()require مقدار خروجیِ یک ماژول دیگر را به کد خود وارد می‌کنند. به مثال زیر توجه کنید.

فرض کنید می‌خواهیم دو فایل به نام های calculator.js و operation.js ایجاد کنیم. فایل calculator.js یک کلاس به نام Arithmetic دارد که می‌خواهیم آن را از calculator.js خروجی بدهیم و بعد در فایل operation.js آن را وارد و از آن استفاده کنیم. فایل calculator.js و operation.js به ترتیب حالات زیر را خواهند داشت:

//calculator.js
class Arithmetic {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
 
    add() {
        return this.a + this.b;
    }
    subtract() {
        return this.a - this.b;
    }
 
    multiply() {
        return this.a * this.b;
    }
 
    divide() {
        if (this.b != 0) {
            return this.a / this.b;
        }
        return "divided by zero !!!!";
    }
};
 
module.exports = Arithmetic;
//operation.js

const Arithmetic = require('./calculator.js');
 
const op = new Arithmetic(100,40);
 
console.log(`Addition -> ${op.add()}`);
console.log(`subtraction -> ${op.subtract()}`);
console.log(`Multiplication -> ${op.multiply()}`);
console.log(`Division -> ${op.divide()}`);

به طور خلاصه در node.js، برای export گرفتن در یک ماژول، وقتی از عبارت module.exports استفاده می‌کنیم که می‌خواهیم تنها یک مقدار (مثلاً یک تابع، یک متغیر یا یک کلاس) را از ماژول خروجی بدهیم. ولی اگر بخواهیم ماژول بیش از یک مقدار را به عنوان خروجی در نظر بگیرد، باید از عبارت exports استفاده کنیم.

منبع مثالی که اخیراً دیدید، این مقاله از سایت geeksforgeeks است. در آنجا می‌توانید درباره‌ی تفاوت‌های exports و module.exports و نوشتار آنها بیشتر بخوانید.

لازم به ذکر است که CommonJS در اصل برای برنامه نویسی سمت سرور (backend) طراحی شده است. در این module system وقتی در یک فایل چندین ماژول را وارد می‌کنیم (require می‌کنیم)، کد ماژول‌ها به صورت همگام (synchronous یا به ترتیب)، یعنی یکی پس از دیگری اجرا و بعد مقادیر خروجیِ آنها بارگذاری می‌شود. این استاندارد مشکل وابستگی‌های چرخه‌ای را به خوبی حل می‌کند.

خوب است بدانیم که روش اصلی تبدیل کدهای node.js به پکیج، با استفاده از همین استاندارد CommonJS است. البته node.js از استاندارد ES Modules نیز پشتیبانی می‌کند که در این رابطه می‌توانید به صفحه‌ی مربوطه از مستند رسمی node.js مراجعه کنید. ما در ادامه‌ی مقاله درباره ی ES Modules بیشتر خواهیم گفت.

اما با این همه، از آنجایی که بارگذاری ماژول‌ها در CommonJS ساختار همگام دارد و از طرفی سرعت بارگذاری در اپلیکیشن‌های frontend بسیار پر اهمیت است، می‌توان گفت با وجود اینکه استاندارد CommonJS برای کدهای سمت سرور بسیار مناسب است، اما این module system برای استفاده در مرورگر چندان خوب نیست. به علاوه اینکه مرورگرها از استاندارد CommonJS پشتیبانی نمی‌کنند، یعنی کدهایی که در قالب CommonJS نوشته شده‌اند، نمی‌توانند به صورت مستقیم توسط مرورگرها تفسیر شوند و برای اینکه بتوانند در مرورگرها اجرا شوند، نیاز به transpile شدن دارند. به دو مورد بالا، این را هم اضافه کنیم که در این استاندارد به ازای هر ماژول یک فایل داریم.

ماژول سیستم AMD (Asynchronous Module Definition)

همانطور که گفتیم، CommonJS برای استفاده در مرورگر مناسب نبود و نارسایی‌هایی داشت. از این رو استاندارد AMD با این رویکرد ایجاد شد که مناسب استفاده در مرورگرها باشد. همان طور که از نامش پیداست، ماژول‌ها در استاندارد AMD به صورت غیرهمگام (asynchronous یا non-blocking) بارگذاری می‌شوند. نمونه کد زیر، مثالی از وارد کردن دو ماژول با نام‌های module1 و module2 در یک اسکریپت است که قرار است با روش AMD آن را اجرا و مدیریت کنیم:

define(['module1', ',module2'], function(module1, module2) {
    console.log(module1.setName());
});

در قطعه کد بالا، function callback زمانی اجرا می‌شود که هر دو ماژول module1 و module2 به صورت کامل بارگذاری شده باشند. همان طور که پیداست، متدِ define به عنوان آرگومان اول، آرایه‌ای از نام ماژول‌های وابستگی را دریافت می‌کند و زمانی که فراخوانده می شود، ابتدا به صورت غیرهمگام و در پس زمینه دو ماژول را بارگذاری می‌کند و هر وقت کار بارگذاری هر دو به صورت کامل تمام شد، function را اجرا می‌کند.

استاندارد AMD با توجه به ساختار غیرهمگامی که دارد، زمان بالا آوردن کد را افزایش می دهد و در نتیجه برای استفاده در مرورگر مناسب‌تر است. در این روش ماژول‌ها می توانند اشیاء، توابع، رشته ها، JSON و ... باشند و از این بابت محدودیتی ندارند. علاوه بر این، در روش AMD ماژول‌ها می‌توانند در فایل‌های جداگانه‌ای باشند. همچنین در این module system مسئله‌ی وابستگی‌های چرخه‌ای (circular dependencies) نیز به خوبی مدیریت می‌شود. این استاندارد بیشتر مخصوص اپلیکیشن های frontend ایجاد شده است.

احتمالاً تا به حال نام RequireJS را شنیده‌اید. کتابخانه‌ی RequireJS، یک module loader است که فایل‌ها و ماژول‌های جاوااسکریپتی را تحت استاندارد AMD بارگذاری می‌کند. این module loader در اصل به گونه‌ای طراحی شده که برای استفاده در مرورگر مناسب باشد. (هر چند می‌توان از آن در سایر محیط‌های جاوااسکریپتی مانند Node نیز استفاده کرد) در واقع RequireJS استاندارد AMD را پیاده سازی کرده است.

در requirejs، تنها کاری که برای وارد کردن فایل‌ها و ماژول‌ها در قالب AMD نیاز است انجام دهیم، استفاده از اسکریپت زیر است:

<script data-main="scripts/main" src="scripts/require.js"></script>

با این پیش فرض که در پوشه ی scripts/ دو فایل وجود دارند، یکی require.js (که همان فایل کتابخانه RequireJS است) و دیگری فایل main.js (که فایل اصلی ماست و می‌خواهیم آن را در قالب AMD اجرا کنیم)

به یک مثال توجه کنید، فرض کنید یک پایگاه کد(code base) داریم که در آن چهار فایل زیر قرار دارند:

  1. Index.html
  2. mainScript.js
  3. toBeImportedScript.js
  4. requirejs.js

فرض کنید فایل mainScript فایل اصلی برنامه‌ی ماست که در درون خودش از فایل toBeImportedScript استفاده می‌کند. حال ما در فایل index.html می‌خواهیم از فایل mainScript.js در قالب AMD استفاده کنیم. برای این کار لازم است در فایل index.html اینگونه بنویسیم:

<!DOCTYPE html>

<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>

</head>

<body>

<div>
  hello world!
</div>

</body>
<!--load require.js with its special data-main attribute-->
<script data-main="mainScript" src="scripts/require.js"></script>


</html>

همانطور که می‌بینید، یک تگ script در نظر می‌گیریم که مقدار src آن را برابر آدرس فایل require.js قرار داده‌ایم و مقدار data-main را برابر آدرس فایل اصلی اسکریپت می‌گذاریم.

اگر در فایل‌های toBeImportedScript.js و mainScript.js به ترتیب داشته باشیم:

define({
  someProp: 'someValue'
})

(فایل toBeImportedScript.js که در آن با استفاده از متد define یک شیء را خروجی داد‌ه‌ایم)

requirejs(['./toBeImportedScript'], (importedObject) => {
  console.log('importedObject is: ', importedObject);
});

(فایل mainScript که در آن ابتدا فایل toBeImportedScript.js را وارد کرده‌ایم و سپس یک لاگ ساده از مقداری که وارد کرده‌ایم را با هم می‌بینیم)

که خروجی صفحه‌ی بالا اینگونه می‌شود:

مثال بالا نمونه‌ای از نوشتار و کاربرد requirejs و پیاده سازی استاندارد AMD بود. درباره‌ی این کتابخانه، می‌توانید توضیحات بیشتر را در وبسایت رسمی آن بخوانید.

ماژول سیستم UMD یا Universal Module Definition

این استاندارد همانطور که از نامش پیداست، به گونه‌ای طراحی شده که هم مناسب backend و هم مناسب frontend باشد. این استاندارد معمولاً به عنوان گزینه‌ی اصلی به کار نمی‌رود. اما به عنوان راهکار جایگزین وقتی از bundler هایی نظیر rollup یا webpack استفاده می‌کنیم کاربرد دارد. 

در زیر یک نمونه از نحوه‌ی تعریف ماژول در این module system را می‌بینید:

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["jquery", "underscore"], factory);
    } else if (typeof exports === "object") {
        module.exports = factory(require("jquery"), require("underscore"));
    } else {
        root.Requester = factory(root.$, root._);
    }
}(this, function ($, _) {
    // this is where I defined my module implementation

    var Requester = { // ... };

    return Requester;
}));

چنان که ملاحظه می‌کنید، این روش بیش از اینکه یک روش مستقل باشد، به نوعی تنظیمِ چند module system در کنار یکدیگر است. یعنی این module system ترکیبی از AMD و CommonJS است و تعیین می‌کند که در لحظه کد باید از چه استانداردی پیروی کند. از این رو استاندارد UMD هم برای سمت سرور و هم برای سمت client کاربرد دارد، اما همان گونه که گفتیم بیشتر از آن به عنوان یک راه حل جایگزین استفاده می‌شود.

همانطور که دقت کرده‌اید، هیچ یک از الگوهای revealing module، CommonJS، UMD و AMD، بومیِ خودِ جاوااسکریپت نیستند و ما باید با اعمال تغییرات و اضافه کردن کدهایی، این module system ها را پیاده سازی کنیم. خوشبختانه ECMAScript6 یک ماژول توکار (built-in) برای جاوااسکریپت معرفی کرده که کارها را بسیار ساده می‌کند. یک ماژول بومی برای جاوااسکریپت به نام ES Module.

ماژول سیستم ESM یا ES Modules

ECMAScript 2015 (یا همان ES6) با ظهور خود یک module system بومی به نام ES Modules برای جاوااسکریپت معرفی کرد که از import و export هم به صورت همگام و هم به صورت غیرهمگام پشتیبانی می‌کند. همین import و export کردن ساده که همه‌ی ما با آن کار کرده‌ایم.

قطعه کد زیر یک نمونه از import و export گرفتن در ES6 است:

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}//------ main.js ------import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

همان طور که گفتیم، قالب ES module استاندارد بومی جاوااسکریپت است. یعنی قالبِ استاندارد و رسمی ایست که برای پکیج کردن کدهای جاوااسکریپت استفاده می‌شود. به دلیل سادگی، بومی و فراگیر بودن این module system، استفاده از این ماژول برای اپلیکیشن‌های سمت کاربر و سمت مرورگر توصیه می‌شود. در واقع ES Module هم ساختار غیرهمگام AMD را دارد، و هم از نوشتار ساده‌ی CommonJS برخوردار است. ES Module به bundler هایی نظیر rollup این اجازه را می‌دهد که کدهای غیرضروری را حذف کنند و به این ترتیب کدهای کمتر و در نتیجه سرعت بیشتری ارائه کنند. برای استفاده از این module system در مرورگر، تنها کافیست به تگِ <script> صفتِ ”type=”module را اضافه کنیم:

<script type="module">
  import {func1} from 'my-lib';

  func1();
</script>

امروزه اکثر مرورگرهای معروف دنیا از این استاندارد پشتیبانی می‌کنند.

قالب‌های mjs. و cjs. چه قالب‌هایی هستند؟

حتماً شما هم تا کنون فایل‌هایی با قالب mjs. یا cjs. دیده‌اید و برایتان جالب بوده که اینها چه فرقی با فایل‌های js. معمولی دارند و چرا در این قالب (format) ها ارائه شده‌اند؟

پاسخ این است که در node.js، قالب mjs. یک پسوند رزرو شده برای فایل‌هاییست که ماژول از نوع ES Module هستند. از آنجا که این فایل‌ها نمی‌توانند با متد ()require به کد وارد شوند، باید این نوع فایل‌ها را با متدی به نام ()import وارد کرد. اینکه این فایل‌ها نمی‌توانند با متد ()require به کد وارد شوند، به دلیل ساختار همگام این متد است.

در رابطه با قالب mjs. پیشنهاد می‌کنیم این بخش از مستند رسمی node.js را مطالعه کنید. همچنین در رابطه با متد ()import و تفاوت آن با کلیدواژه‌ی import (یعنی import به تنهایی و بدون پرانتز)، می‌توانید این مستند از سایت MDN را مطالعه کنید.

فرمت cjs. که مخفف Common js است نیز از قبل مشخص می‌کند که یک فایل از استاندارد CommonJS پیروی می‌کند.

درباره‌ی این که node.js با هر یک از قالب‌ها با چه روشی برخورد می‌کند، می توانید این بخش از مستند رسمی node.js را مطالعه کنید.

در مورد module system هایی که در این مقاله توضیح دادیم، مطالعه‌ی این مقاله در سایت dev.to را پیشنهاد می‌کنیم که با بیانی دیگر و به صورت خلاصه به توضیح این ماژول‌ها و ارائه‌ی مثال‌هایی برای هر یک می‌پردازد.

نتیجه گیری

به عنوان یک برنامه نویس جاوااسکریپت، حتماً یا به صورت مستقیم و یا غیرمستقیم با مفاهیم module system یا عبارات مرتبط با آن‌ها زیاد سر و کار داشته‌اید. قطع نظر از اینکه یک توسعه دهنده‌ی frontend هستید، یا با استفاده از node.js کدهای backend می‌زنید، یا به هر طریق دیگری از جاوااسکریپت استفاده می‌کنید، دانستن در مورد module system درک شما را عمیق‌تر و دید شما را بازتر می‌کند.

اگر با خواندن این مقاله به نظرتان رسید که مبحث module system قدری تو در تو و پیچیده است، اصلاً نگران نباشید. چرا که امروزه با پیشرفت و توسعه‌ی ابزارهای bundler و CLI های مختلفی که برای تکنولوژی‌ها آمده، و همچنین قدرت حدس کد code editor ها و IDE ها و افرونه‌های مختلف آنها، import کردن‌های هوشمند و سایر ابزارهای کمکی که برای توسعه‌ی برنامه‌ها وجود دارد، کم پیش می‌آید که ما به صورت مستقیم module system ای را تنظیم کنیم. با این وجود گفتیم که اگر با این مفاهیم آشنا باشیم، قدمی به سمت حرفه‌ای‌تر شدن برداشته‌ایم و دیگر ترسی در مواجهه با آنها نخواهیم داشت.

به طور خلاصه می‌توان گفت:

  • ESM با برخورداری از نوشتار (syntax) ساده، ساختار غیرهمگام (asynchronous)، بومی بودن و همچنین قابلیت درخت تکانی که دارد، در بسیاری اوقات بهترین گزینه برای module system است.
  • AMD ساختار غیرهمگام دارد و در مجموع برای frontend مناسب است.
  • CJS ساختار همگام دارد و معمولاً در کدهای سمت سرور از آن استفاده می‌شود.
  • UMD که در همه جا کار می‌کند، معمولاً در جاهایی که ESM پاسخگوی نیازهای ما نیست به عنوان راه حل جایگزین استفاده می‌شود.

به این نکته هم توجه داشته باشید چه زمانی که یک ماژول را با استاندارد CJS وارد می‌کنیم (با استفاده از ()require) و چه زمانی که یک ماژول را با استاندارد ESM وارد می‌کنیم (با استفاده از import)، ابتدا کد مربوط به ماژول اجرا می‌شود، و بعد خروجی در اختیار کدِ import کننده قرار می‌گیرد.

در آخر نیز خوب است در مورد همگام بودن یا غیرهمگام بودنِ وارد کردن کدها توضیح کوتاهی بدهیم. (یعنی اینکه نحوه ی import کردن یک ماژول به صورت synchronous است یا به صورت asynchronous)

گفتیم که در CommonJS نحوه‌ی وارد شدن ماژول‌ها به یک اسکریپت همگام است، یعنی خط به خط و به ترتیب. فرض کنید یک فایل به نام mainCJS داریم و می‌خواهیم در این فایل دو ماژولِ testCJS1 و testCJS2 را وارد کنیم. 

محتوای این فایل‌ها به این ترتیب هستند:

//mainCJS.js
const test1 = require('./testCJS1');

const test2 = require('./testCJS2');


console.log('test1: ', test1);
console.log('test2: ', test2);

(در فایل mainCJS که اسکریپت اصلی ماست، ابتدا خروجی testCJS1 و سپس خروجی testCJS2 را در متغیرهای test1 و test2 ریخته‌ایم و بعد از آنها لاگ گرفته‌ایم)

//testCJS1.js
console.log('in the testCJS1')

function sleep(delay) {
  var start = new Date().getTime();
  while (new Date().getTime() < start + delay);
}

sleep(2000);

module.exports = 'from testCJS1';

(در این فایل ابتدا یک لاگ تستی گرفته‌ایم، سپس یک تابع به نام sleep تعریف کرده‌ایم که با استفاده از یک حلقه while، اجرای کد را به مدت 2 ثانیه متوقف می‌کند. سپس آن را فراخوانی کرده و خروجی آن را در module.exports ریخته‌ایم)

//testCJS2.js
console.log('in the testCJS2')


module.exports = 'from testCJS2';

(این فایل یک فایل ماژول ساده‌ی CommonJS است که فقط یک لاگ در ابتدا دارد و بعد یک مقدار را خروجی می‌دهد)

حال اگر با node.js فایلِ mainCJS را اجرا کنیم (اجرای دستور node mainCJS)، عبارات به ترتیب زیر چاپ می‌شوند:

in the testCJS1
// (here the code goes to sleep for 2 seconds)
in the testCJS2
test1:  from testCJS1
test2:  from testCJS2

همانطور که گفتیم با require کردن یک ماژول، ابتدا کد آن ماژول فراخوانی می‌شود. پس ابتدا عبارت: “in the testCJS1“ چاپ می‌شود. سپس کد به مدت دو ثانیه متوقف می‌شود و بعد کد ماژولِ testCJS2 اجرا می‌شود. پس دقت کنید تا زمانی که اجرای ماژول testCJS1 به پایان نرسید، اسکریپت mainCJS شروع به اجرای testCJS2 نکرد. این همان مفهوم همگامی‌ست که گفتیم در CommonJS وجود دارد.

حال به یک مثال مشابه از ES Module توجه کنید. فرض کنید یک فایل mainESM.js داریم که می‌خواهیم در این اسکریپت ماژول‌های testESM1.js و testESM2.js را وارد کنیم. محتوای این سه فایل به ترتیب عبارتند از:

//mainESM.js
import test1 from './testESM1.js';

import test2 from './testESM2.js';

console.log('test1: ', test1);
console.log('test2: ', test2);

(مشابه مثال قبل، این فایل test1 و test2 را وارد می‌کند و از خروجی آنها لاگ می‌گیرد)

//testESM1.js
console.log('in the testESM1')

const sleep = new Promise(resolve => {
  setTimeout(() => {
    resolve('from testESM1');
  }, 2000)

})

await sleep;


export default 'from testESM1';

(در این فایل یک promise تعریف کرده‌ایم که پس از 2 ثانیه کارش به اتمام می‌رسد (resolve می‌شود)، سپس با استفاده از await منتظر می‌مانیم این promise شروع به کار کند و کارش تمام شود، سپس یک رشته را به عنوان خروجی در نظر می‌گیریم)

//testESM2.js
console.log('in the testESM2')

export default 'from testESM2';

(این فایل نیز صرفاً یک لاگ دارد و بعد یک رشته به عنوان خروجی)

حالا اگر mainESM.js را در قالب ES Module اجرا کنیم، مثلاً یک فایل html ایجاد کنیم و در یک تگِ

 <script type=”module” src=””></script>

قرار دهیم (در مقابل src آدرس فایل mainESM را می دهیم)، خروجی چاپ شده به این ترتیب خواهد بود:

in the testESM1
in the testESM2
// (here the code goes to sleep for 2 seconds)
test1:  from testESM1
test2:  from testESM2

همان طور که می‌بینید، mainESM ابتدا شروع به اجرای testESM1 می‌کند، پس عبارت “in the testESM1” چاپ می‌شود. سپس با توجه به اینکه ES Module از حالت asynchronous پشتیبانی می‌کند، به صورت موازی کد testESM2 را نیز شروع به اجرا می‌کند و بنابراین عبارت “in the testESM2” در مرحله‌ی بعد چاپ می‌شود. در این قسمت از آنجا که هنوز ماژول testESM1 به خروجی مد نظر نرسیده است، کد صبر می‌کند تا کار آن به پایان برسد. بعد که خروجی ماژولِ اول، یعنی خروجی ماژول testESM1 آماده شد، لاگ‌های test1 … و test2 … چاپ می‌شود.

یعنی در ES Module، کدِ ماژول‌های مختلفی که توسط یک فایل import می‌شوند، به صورت غیرهمگام اجرا می‌شوند، اما رفتارش نسبت به خروجی‌ای که از آنها دریافت می‌کند (در متغیر می‌ریزد و بعداً قرار است از آنها استفاده کند) همگام خواهد بود، یعنی صبر می‌کند تا ابتدا خروجی ماژول اول آماده شود و بعد به سراغ خروجی ماژول دوم می‌رود.

خوب است این قطعه کد را یک بار در سیستم خود اجرا کنید و نتیجه را ببینید.

از بهترین نوشته‌های کاربران سکان آکادمی در سکان پلاس


online-support-icon