ماژولها در ES 6
هدف از سیستم ماژولها
در ES 6، مدیریت بهتر تعدادی قطعه کد جاوا اسکریپتی، به صورت یک واحد مشخص است. همچنین ماژولها امکان مخفی کردن قسمتهایی از کد را که نباید به صورت عمومی در دسترس قرارگیرند، نیز میسر میکنند. این مسایل سالها آرزوی برنامه نویسان جاوا اسکریپت بودهاند و برای برآورده کردن آنها به روشهای غیراستاندارد و کتابخانههای ثالثی روی آورده بودند. به همین جهت برای آشنایی بهتر با ماژولها در ES 6، ابتدا نیاز است با روشهای متداول فعلی بسته بندی کدها در جاوا اسکریپت آشنا شد.
روش IIFE Module
الگوی ماژولها، سالها است که در جاوا اسکریپت مورد استفادهاست:
(function(target){
var privateDoWork = function(name) {
return name +" is working";
};
var Employee = function(name) {
this.name = name;
}
Employee.prototype = {
doWork: function() {
return privateDoWork(this.name);
}
}
target.Employee = Employee;
}(window));
IIFE یا immediately invoked function expression، متدی خود اجرا شونده است. به صورت پیش فرض هر متغیری که داخل IIFE تعریف میشود، سطح دید آن محدود است به بدنهی همان ماژول و دیگر دسترسی عمومی ندارد. برای مثال شیء Employee فقط داخل این ماژول قابل دسترسی است. تنها زمانیکه توسط target.Employee، این شیء را به window متصل میکنیم، خارج از این ماژول قابل دسترسی میشود.
بنابراین با روش IIFE به مزیتهای یک سیستم ماژول میرسیم:
الف) امکان مدیریت کدها را به صورت یک unit و واحد فراهم میکند.
ب) همچنین در اینجا امکان کنترل میدان دید متغیرها و متدها نیز میسر است.
روش CommonJS
از سال 2009 استفاده از جاوا اسکریپت به خارج از مرورگرها گسترش یافت؛ برای مثال نوشتن برنامههای سمت سرور NodeJS یا MongoDB با جاوا اسکریپت. در یک چنین حالتی برای مدیریت پیچیدگی برنامههای گستردهی سمت سرور و پرهیز از متغیرها و اشیاء عمومی، پروژهی
CommonJS شکل گرفت. در CommonJS نحوهی تعریف ماژولها بسیار شبیه است به IIFE. با این تفاوت که دیگر خبری از متد خود اجرا شونده وجود ندارد و همچنین بجای target از exports، جهت درمعرض دید قرار دادن اشیاء استفاده میکند.
var privateDoWork = function(name) {
return name +" is working";
};
var Employee = function(name) {
this.name = name;
}
Employee.prototype = {
doWork: function() {
return privateDoWork(this.name);
}
}
exports.Employee = Employee;
این تعاریف در یک فایل مجزای JS قرار میگیرند. سپس برای دسترسی به آنها در فایلی دیگر، از روش ذیل استفاده میکنند:
var Employee = require("./Employee").Employee;
var e1 = new Employee("Vahid");
console.log(e1.doWork());
در متد require، مسیر فایل و ماژول تعریف شده، مشخص میشود؛ بدون ذکر پسوند .js فایل.
روش AMD
از CommonJS بیشتر در برنامههای جاوا اسکریپتی که خارج از مرورگر اجرا میشوند، استفاده میشود. برای حالتهای اجرای برنامهها درون مرورگرها و خصوصا بلاک نشدن ترد نمایش صفحه در حین پردازش ماژولها، روش دیگری به نام AMD API و یا Asynchronous module definition به وجود آمد. پیاده سازی محبوب این API عمومی، توسط کتابخانهای به نام
RequireJS انجام شدهاست.
define(function(){
var privateDoWork = function(name) {
// ...
};
var Employee = function(name) {
// ...
}
return Employee;
});
در اینجا یک ماژول تعریف شدهی در یک فایل مجزای جاوا اسکریپتی با define function شروع میشود و در نهایت یک return دارد.
تفاوت مهم این روش با روش IIFE این است که در روش IIFE تمام کد باید مهیا بوده و همچنین بلافاصله قابل اجرا باشد. در اینجا تنها زمانیکه نیاز به کار با ماژولی باشد، اطلاعات آن بارگذاری شده و استفاده میشود.
برای استفادهی از این ماژولها نیز از همان define استفاده میشود و پارامتر اول ارسالی، آرایهای است که ماژولهای مورد نیاز را تعریف میکند (تعریف وابستگیها). برای مثال employee تعریف شده در اینجا سبب بارگذاری فایل employee.js میشود و سپس امکانات آن به صورت یک پارامتر، به متدی که به آن نیاز دارد ارسال میگردد:
define(["employee"], function(Employee){
var e = new Employee("Vahid");
});
ماژولها در ES 6
سیستم تعریف ماژولها در ES 6 بسیار شبیه است به روشهای CommonJS و AMD API. در اینجا یک نمونه از روش تعریف ماژولها را در نگارش جدید جاوا اسکریپت مشاهده میکنید:
export class Employee {
constructor(name) {
this[s_name] = name;
}
get name() {
return this[s_name];
}
doWork() {
return `${this.name} is working`;
}
}
در اینجا واژهی کلیدی export سبب در دسترس قرار گرفتن تعریف
یک کلاس تعریف شدهی در این ماژول که در اینجا یک فایل جاوا اسکریپتی است، میشود. در یک فایل میتوان چندین export را داشت؛ اما بهتر است یک export را به ازای هر فایل درنظر گرفت.
پس از این export، اکنون برای استفادهی از آن در یک فایل js دیگر، از واژهی کلیدی import کمک گرفته میشود:
import {Employee} from "./employee";
var e1 = new Employee("Vahid");
console.log(e1.doWork());
در اینجا پس از from، مسیر فایل js، بدون ذکر پسوند آن مشخص میشود.
و یا برای ارائهی یک متد خروجی، به نحو ذیل عمل میشود:
export function multiply (x, y) {
return x * y;
};
و اگر یک متغیر و یا متد تعریف شدهی در سطح ماژول را بخواهیم عمومی کنیم، باید از {} استفاده شود:
var hello = 'Hello World',
multiply = function (x, y) {
return x * y;
};
export { hello, multiply };
حالت پیش فرض ماژولهای ES 6 همان strict mode است
در سیستم ماژولهای ES 6،
حالت strict به صورت پیش فرض روشن است. برای مثال متغیرها حتما باید تعریف شوند.
امکان تعریف خروجیهای متفاوت از یک ماژول در ES 6
در همان فایلی که export class Employee فوق را در آن تعریف کردهایم، یک چنین تعریفهایی را نیز میتوان ارائه داد:
export let log = function(employee) {
console.log(employee.name);
}
export let defaultRaise = 0.03;
export let modelEmployee = new Employee("Vahid");
در اینجا نحوهی export متد log و یا متغیر defaultRaise و همچنین شیء modelEmployee را مشاهده میکنید. سپس برای استفادهی از این خروجیها، قسمت import نیز باید به نحو ذیل تغییر کند:
import {Employee, log, defaultRaise, modelEmployee} from "./employee";
log(modelEmployee);
برای ساده سازی دریافت تمام خروجیهای یک ماژول ES 6، میتوان از واژهی کلیدی module استفاده کرد:
module m from "./employee";
در اینجا متغیر m امکان دسترسی به Employee, log, defaultRaise, modelEmployee را بدون نیاز به ذکر آنها در قسمت import میسر میکند. در یک چنین حالتی برای دسترسی به خروجیها، از .m استفاده میشود. برای مثال:
console.log(m.defaultRaise);
و یا
var e1 = new m.Employee("Vahid");
console.log(e1.doWork());
روش دیگر انجام اینکار، استفاده از * است برای درخواست تمام وابستگیهای مورد نیاز:
import * from "./employee";
امکان استفاده از یک ماژول در ماژولی دیگر
برای اینکه از امکانات یک ماژول در ماژولی دیگر استفاده کنیم نیز میتوان از همان روش تعریف import در ابتدای ماژول استفاده کرد:
import {Employee} from "./employee";
امکان تعریف ماژول پیش فرض در ES 6
اگر ماژول شما (همان فایل js) تنها دارای یک export است، میتوانید آنرا با واژهی کلیدی default مشخص کنید:
export default class Employee {
به این ترتیب برای استفادهی از این ماژول تنها کافی است بنویسیم:
import factory from "./employee";
var e1 = new factory("Vahid");
console.log(e1.doWork());
در اینجا factory یک نام متغیر دلخواه است و هر نام دیگری را نیز میتواند داشته باشد.
البته باید دقت داشت که یک چنین تعریفهایی نیز مجاز است و میتوان خروجی پیش فرض و همچنین نامداری را نیز با هم ترکیب کرد:
export hello = 'Hello World';
export default function (x, y) {
return x * y;
};
در این حالت تعریف ذیل به این معنا است که pow2 به متد پیش فرض بدون نام و hello به متغیر hello اشاره میکنند:
import pow2, { hello } from 'modules';
امکان مخفی سازی اطلاعات در ماژولهای ES 6
یکی از انتظارات از سیستم ماژول، امکان مخفی سازی اطلاعات است. در اینجا تنها کافی است شیء، متد و یا متغیر تعریف شده، با واژهی کلیدی export مزین نشوند:
let privateFunction = function() {
}
export default class Employee {
در این مثال، متد privateFunction در ماژول employee تعریف شدهاست؛ اما چون دارای واژهی کلیدی export نیست، سطح دسترسی آن خصوصی است.
یک نکته: اگر در کلاس export شده، خواستید تا دسترسی به s_name را محدود کنید،
از Symbol ها به نحو ذیل کمک بگیرید:
let s_name = Symbol();
export class Employee {
constructor(name) {
this[s_name] = name;
}
get name() {
return this[s_name];
}
doWork() {
return `${this.name} is working`;
}
}