پیشنهاد می کنم ابتدا مقدمه این سری از مقالات رو بخونید تا پیش زمینه خوبی داشته باشین برای شروع لینک
برای درک این موضع که چرا در حال حاضر زبان جاوا اسکریپت به این گستردگی استفاده میشه لازمه بدونیم که در طول زمان این زبان چگونه تکامل پیدا کرده .
این نقل قول از آقای Eric Lippert ، توسعه دهنده سابق جاوا اسکریپت و مرورگر IE در مایکروسافت که برای توضیح ابتدای پیدایش و توسعه جاوا اسکریپت است قابل توجه هستش :
هدف طراحی جاوا اسکریپت این بود که وقتی که موس روی میمون رفت شروع به رقصیدن کند. ( المنت های صفحه اینترکتیو و زنده شوند ). در ابتدا اسکریپت ها اغلب یک خط بودند. ما ده خط اسکریپت را کاملاً عادی در نظر گرفتیم، صد خط را بزرگ و هزاران خط اسکریپت برای ما ناشناخته بود. این زبان مطلقاً برای برنامه نویسی در مقیاس بزرگ طراحی نشده بود و تصمیمات اجرایی ما، اهداف ، عملکرد و... بر اساس این فرض بود.
این تعریف از سایت https://javascript.info هم میتونه بعضی چیز هایی رو که در بالا اشاره شدن رو برای شما روشن تر کنه :
جاوا اسکریپت در ابتدا برای زنده کردن صفحات وب ایجاد شد و به برنامه های که با این زبان نوشته میشوند اسکریپت می گویند. اسکریپت ها به صورت متن ساده ( plain text ) ارائه و اجرا می شوند. آنها برای اجرا نیاز به آماده سازی یا ویرایش خاصی ندارند.
امروزه برنامه های بزرگ نوشته شده به این زبان ممکن است از صدها هزار خط کد تشکیل شده باشند . که این ویژگی یک رویکرد کاملاً متفاوت برای مدیریت کردن محدودیتها ، نحوه نوشتن این زبان ، ساخت و ارائه برنامهها را لازم دارد. به زبان ساده تر یعنی اینکه با توجه به بزرگی اپلیکیشن های موجود برای توسعه و نوشتن این زبان در مقایسه با به زمان ابتدایی استفاده از آن بایست در زمینه های نوشتن درست کد ها و اجرای بهینه آنها و ساخت و نگه داری آنها رویکرد جدیدی را توسعه دهندگان این زبان در پیش بگیرن !
این محدودیت ها منجر به رویکرد توسعه بسیار متفاوتی نسبت به روزهای اولیه شده و توسعه دهندگان وب مدرن امروزی به جای نوشتن چند خط کد جاوا اسکریپت و قرار دادن آنها در یک صفحه HTML برای اجرا شدن، از زنجیرهای از ابزارهای پیچیده ساخت استفاده می کنند که معادل کامپایلرهای زبان هایی مانند C++ یا جاوا هستند.
تقریباً همه زبانها دارای سینتکسی به خصوص برای استفاده و اعلام «ماژولها» یا «پکیج ها» هستند.
برای توسعه دهنده هایی که در ابتدای راه هستند لازمه که ذکر کنم ماژول در جاوا اسکریپت یک فایل حاوی کد های مرتبط است ( فایلی است که درون خودش قطعه کدی دارد که ما میخواهیم آن قطعه کد را وارد فایل حاضر خودمان کنیم ) که برای استفاده از کد ها و توابع این فایل ، آن را وارد فایلی که اکنون در آن هستیم میکنیم ( منابع برای مطالع بیشتر منبع اول و منبع دوم )
به عنوان مثال، یک فایل در زبان جاوا ممکن است اعلام کند که بخشی از pakage com.my.project است و
سپس با دستور import some.other.project.SomeClass یک وابستگی به پکیج دیگری را شما اعلام کنید.
زبان های C#، Python، Go، و Swift همگی تعریف پکیج و دستور import/export مخصوص خود را دارند.
اما جاوا اسکریپت یکی از این زبان ها نیست !
برخلاف تمام این زبانها، جاوا اسکریپت در ابتدا هیچ ساختار قالب ماژول داخلی نداشت. در سال های ابتدایی پیدایش این زبان، کد های جاوا اسکریپت به صورت تگهای درون خطی ( inline) مستقیماً در HTML یا بهعنوان فایلهای js کوچک با چند متغیر گلوبال مشترک نوشته میشد. با پیشرفت دنیای وب و بزگتر شدن کد بیس های برنامه های جاوا اسکریپتی، جامعه توسعه دهندگان این زبان شروع به اختراع قالب های ماژول خود کردند تا به ارائه ساختار برای کدها و کپسوله سازی( ایزوله سازی) کد ها کمک کنند. که هر یک از این فرمت ها برای حل مشکلات و موارد استفاده ( use case ) مختلف اختراع شده بودند.
اضافه کردن چندین تگ اسکریپت به یک صفحه میتونه مشکلات متعددی رو بوجود بیاره ! تعیین وابستگی بین فایل های اسکریپت مختلف و بارگذاری آنها به ترتیب مناسب می تواند کار را بسیرا سخت کنه و همچنین، از آنجایی که همه متغیرهای سطح بالا ( top-level variables ) فضای نام ( namespace ) گلوبال یکسانی را اشغال می کنند، بسیار قابل پیش بینی هست که به طور تصادفی متغیرهایی با نام یکسان بر یکدیگر تاثیر بزران و باگ های عجیبی رو بوجود بیارن !
مثال زیر رو ببینید که متغیر delay رو در دو ماژول مختلف با مقادیر مختلف داریم ! و در هنگام استفاده از آن ممکن است به باگ بخوریم ...
<script src="jquery.min.js"> <script src="jquery.someplugin.js"> <script src="./components/dropdown.js"> <script src="./components/modal.js"> <script src="./application.js"> // dropdown.js var delay = 2000; // in ms // modal.js var delay = 4000; // in ms // application.js // Oops - is it 2000 or 4000? console.log(delay)
برای حل مشکلات اینچنینی از دو الگو و پترن توابع IIFE (با تلفط "ایفی") و AMD استفاده می شد !
توابع IIFE یا Immediately Invoked Function Expressions الگویی هستند که متکی به متغیرهای JS هستند که به نزدیکترین تابع محدود می شوند. IIFE شامل تعریف یک تابع جدید و سپس فراخوانی فوری آن برای به دست آوردن نتیجه مورد نظر است. IIFE به تابعی گفته میشه که به محض تعریف شدن، اجرا میشه. سینتکس نوشتن اون به صورت زیر است ( منابع برای مطالعه بیشتر : منبع اول و منبع دوم )
( function () { })();
توابع IIFE کپسولهسازی را فراهم میکنند و جایی که یک IIFE یک آبجکتی را بازگشت بدهد که ساختار یا API عمومی آن را تعریف کرده است ، به عنوان مبنای الگوی revealing module ( "ماژول آشکار" ) استفاده میشود، (و معادل یک تابع فاکتوری یا کلاس کانستتراکتور هستند):
// dropdown.js (function(){ var delay = 2000; // in ms APP.dropdown.delay = delay; }()); // modal.js const modalAPI = (function(){ // No name clash - encapsulated in the IIFE var delay = 4000; // in ms APP.modal.delay = delay; $("#myModal").show(); function hideModal() { $("#myModal").hide() } // return a "public API" for the modal by exposing methods return { hideModal : hideModal } }());
الگو AMD یا Asynchronous Module Definition به طور خاص برای استفاده توسط مرورگرها طراحی شده بود . نحوه کار آن به این صورت است که در ابتدا یک کتابخانه مخصوص که کار آن لود کردن ماژول های AMD است ، ابتدا یک تابع به نام define را به صورت گلوبال ایجاد می کند و سپس ماژولهای AMD، تابع define را فراخوانی میکنند و در آرایهای از نامهای ماژول که به آن وابسته هستند و تابعی که به عنوان بدنه ماژول عمل میکند، عبور میکنند. تابع بدنه ماژول تمام وابستگی های درخواستی ( requested dependencies ) را به عنوان آرگومان ورودی دریافت می کند و ممکن است هر مقدار را به عنوان "خروجی" یا export خود برگرداند. سپس کتابخانه لودر بررسی می کند که آیا تمام وابستگی های درخواستی ثبت و بارگذاری شده اند یا خیر. اگر این کار به درستی انجام نشده باشد، به صورت بازگشتی وابستگیهای دیگر را به سبک آبشاری دانلود میکند، و راه خود را به زنجیره وابستگی برمیگرداند تا هر تابع ماژول را با وابستگیهایش مقداردهی اولیه کند.
// moduleA.js // Loader library adds a global `define()` function define(["jquery", "myOtherModule"], function($, myOtherModule) { // Body of the function is the module definition const a = 42; const b = 123; function someFunction() { } // Return value is the "exports" of the module // Can do "named exports" by returning object with many values return {a : a, publicName : b, someFunction : someFunction} }); // moduleB.js define(["backbone"], function(Backbone) { const MyModel = Backbone.Model.extend({}); // Can do a "default" export by just returning one thing // instead of an object with multiple things inside return MyModel; });
توضیحات بالا عجیب و غریب و گنگ بود نه ؟! :|
بزارین کمی ساده ترش کنیم ، کد زیر رو مشاهده کنید :
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) { console.log(myModule.hello()); });
آنچه در اینجا اتفاق می افتد این است که تابع define آرایه ای از هر یک از وابستگی های ماژول را به عنوان اولین آرگومان خود می گیرد ، سپس این وابستگیها در background بارگذاری میشوند (به شیوه non-blocking)، و پس از لود کامل، تابع کال بک درونی را فراخوانی میکند.آنگاه در این مرحله، تابع callback به عنوان آرگومان، وابستگی هایی را که بارگذاری شده اند می گیرد ( در مثال بالا، myModule و myOtherModule )و می تواند از این وابستگی ها استفاده کند ( منبع برای مطالع بیشتر منبع اول ، منبع دوم ، منبع سوم )
در حال حاضر الگو های IIFE و AMD دیگر به عنوان الگو های استفاده از ماژول ها در جاوا اسکریپت استفاده نمی شوند ، ولی کد بیس هایی قدیمی هنوز وجود دارند که از این الگو ها استفاده می کنند .
اسم این فرمت رو اگر با جاوا اسکریپت از قبل آشنا باشین احتمالا شنیدید، فرمت ماژول CommonJS به طور خاص برای استفاده در Node.js ایجاد شد . ( Node.js یک ران تایم و محیط اجرای را برای کد های جاوا اسکریپت در خارج از مرورگر فراهم میکند) .از آنجایی که Node به سیستم فایل دسترسی دارد ، یعنی میتواند فایل ها را از روی حافظه سیستم بخواند، فرمت CommonJS برای بارگیری همزمان ( synchronously ) ماژول ها از دیسک به محض وارد کردن آنها نیز طراحی شده است.
مفسر Node.js برای انجام عملیات ایمپورت و تزریق یک فایل ، یک تابع به نام require
به صورت گلوبال را تعریف می کند که این تابع مسیرهای نسبی، مسیرهای مطلق یا نام کتابخانه را می پذیرد. سپس Node از یک فرمول جستجوی پیچیده استفاده می کند تا فایلی مطابق با مسیر/ نام داده شده را پیدا کند و در صورت یافتن، بلافاصله فایل درخواستی را خوانده و بارگذاری می کند.
برای انجام عملیات export ، ماژول های CommonJS هیچ تابع ای مانند تابع require را ندارد ، برای انجام عملیات export مفسر یک متغیر گلوبال و عمومی module.exports را تعریف می کند و ماژول مقادیری که میخواد export کند را ، با اختصاص دادن به آن متغیر به بیرون ارسال می کند.
// moduleA.js // Node runtime system adds `require()` function and infrastructure const $ = require("jquery"); const myOtherModule = require("myOtherModule"); // The entire file is the module definition const a = 42; const b = 123; function someFunction() { } // Node runtime adds a `module.exports` keyword to define exports // Can do "named exports" by assigning an object with many values module.exports = { a : a, publicName : b, someFunction : someFunction } // moduleB.js const Backbone = require("backbone"); const MyModel = Backbone.Model.extend({}); // Can do a "default" export by just assigning // one value to `module.exports` module.exports = MyModel;
فرمت ماژول CommonJS امکان وارد کردن داینامیک و پویا ماژولهای را در هر زمان میدهند و میتوانند به صورت مشروط نیز عملیات ایمپورت را انجام دهند.
طرف دیگرماجرا این است که ماژول های CommonJS را نمی توان در مرورگر است استفاده کرد - به نوعی آداپتور و ... نیاز است برای این کار.
که مخفف Universal Module Definition است . نیاز بود که برخی از کتابخانهها بتوانند در محیطهای متعدد با قابلیت های یکسان استفاده شوند .مثلا به عنوان یک تگ گلوبال ساده یا به عنوان یک ماژول AMD در یک مرورگرباشد و یا یک فایل CommonJS در Node باشد. برای حل این مشکل جامعه توسعه دهندگان یک هک عجیب و غریب اختراع کرند که به یک ماژول اجازه می داد در هر سه محیط با قابلیت های تشخیص ویژگی های آن محیط به درستی کار کند. که این فرمت به نام UMD نامگذاری شد .
// File log.js (function (global, factory) { if (typeof define === "function" && define.amd) { define(["exports"], factory); } else if (typeof exports !== "undefined") { factory(exports); } else { var mod = { exports: {} }; factory(mod.exports); global.log = mod.exports; } })(this, function (exports) { "use strict" function log() { console.log("Example of UMD module system"); } // expose log to other modules exports.log = log; });
از این فرمت ماژول امروزه کم و بیش استفاده می شود
خب بالاخره به اون چیزی که میخواستیم رسیدیم ، فرمت ماژولی که مخصوصا در سمت کلاینت بسیار ازش استفاده میشه همین فرمت ماژول ES است !
در ES2015 در نهایت این فرمت ماژول به عنوان دستور ماژول رسمی را به زبان JS اضافه شد.که اکنون به عنوان ES Modules یا "ESM" شناخته می شود. این فرمت دستوری را برای تعریف import ها و export ها به صورت نامگذاری شده ( named )و پیش فرض( default ) ارائه می دهد.
به دلیل تفاوت بین مرورگرها و Node.js، این فرمت ماژول مشخصات نحوه لود شدن ماژول ها توسط یک مفسر را مشخص نمی کند ، یا اینکه مشخص کند که رشته های import به چه چیزی اشاره می کنند، و در عوض آن را به ران تایم های مختلف واگذار می کند تا خودشان درک کنند که چگونه ماژول ها را به درستی لود کنند.
همه مرورگرهای مدرن لود کردن ماژول های ES را بر اساس URL ها به عنوان رشته های import پیاده سازی کرده اند. اما Node به دلیل تکیه بر ماژول های CommonJS به عنوان فرمت پیش فرض، به طور قابل توجهی در حرکت رو به جلو برای بکار گیری کامل این فرمت ماژول با مشکل بیشتری روبرو بوده است.
از ورژن پانزدهم Node از لود ماژولهای ES پشتیبانی میکند، اما هنوز مشکلاتی در تعیین نحوه تعامل فایلهای CommonJS و ES Module با یکدیگر وجود دارد.( که احتمالا در ورژن های بالاتر این مشکلات به مرور از بین می روند )
ماژول های ES به گونه ای طراحی شدند که قابل تجزیه و تحلیل باشند ، همچنین نکته منفی این است که نمی توانید import های دینامیک یا مشروط را با این فرمت ماژول انجام بدید !
// moduleA.js // ES6 language spec defines import/export keywords import $ from "jquery" // Can do "default" imports - no curly braces around the variable name import myOtherModule from "myOtherModule" // Define "named exports" by adding `export` in front of a variable export const a = 42; export const b = 123; export {b as publicName}; export function someFunction() { } // moduleB.js // Can do "named imports" from other modules import {Model} from "backbone" const MyModel = Model.extend({}); // Can do a "default" export with the `export default` keyword export default MyModel;
برای اینکه این پست خیلی طولانی نشه ادامه این مبحث رو در پستی مجزا منتشر میکنم