УВИТ - Програмски језик ЈаваСкрипт
Модули
Модуларни концепт програмирања је тренутно један од најзаступљенијих концепата у модерном ЈаваСкрипт програмирању. Заснива се на разбијању једне велике апликације на мање, енкапсулиране, независне делове кода – модуле. Циљ модуларности јесте да се смањи комплексност према принципу “подели па владај”.
Најзначајније предности модуларног програмирања су:
-
прегледнија и разумљивија апликација
-
лакше контролисање опсега дефинисаности промењивих
-
спречавање да глобални опсег дефинисаности буде “загађен”
-
поновна употребљивост кода
-
могућност рада на истом пројекту више различитих тимова или програмера који раде одвојене мање задатке.
-
лакше дебагирање
У зависности од верзије ЈаваСкрипта постоје различити приступи реализацији модуларног програмирања, па самим тим и различите синтаксе које омогућавају модуларно програмирање:
-
Нативна ES5 синтакса
-
ES5 синтакса са спољним библиотекама
-
Синтакса ES6 модула
Проблем учитавања модула није тривијалан (модули зависе од других модула, треба се ефикасно реализовати, итд.) па се за решавање овог проблема користе посебни програми тзв. алати за учитавање модула (енгл. module loader) и алати за увезивање модула (енгл. module bundler).
Нативни ES5 модули
У време писања ЈаваСкрипта, модуларно програмирање није било планирано као начин програмирања, тако да ЈаваСкипт све до верзије ES6 (јиш се означава и са ES2015) нема уграђену подршку за модуле. У оквиру нативног ES5 се користе разни обрасци (енгл. pattern) који имају сличности са модуларним програмирањем, али који немају баш све карактеристике “чистокрвног модуларног шаблона”.
Ефекат модула код којих је енкапсулиран њихов код је у овом случају могу постићи на да начина: коришћењем функцијских израза који се одмах изршавају или коришћењем конструктора.
У оба случаја, основна идеја је иста: оно што треба да буде доступно спољашњости се враћа из функције помоћу резервисане речи return
. Овај једноставан шаблон се може примењивати било где и не захтева коришћење додатних библиотека. Надаље, у оквиру једне датотке је, ако је то потребно, могуће дефинисати више модула.
Поред наведених предности овај приступ ипак има и својих мана:
-
“загађивање” глобалног опсега имена називима модула (мада је тело модула сакривено у локални домен коришћењем функција).
-
потреба за “ручним” одређивањем редоследа учитавања модула, што може бити прилично компликовано код великих и комплексних апликација.
-
немогућност асихроног учитавања модула
Модули преко функцијских израза који се одмах извршавају
Функцијски изрази који се одмах извршавају (енгл. Immediately-Invoked Function Expressions - IIFE (изговара се “ифи”) ), су функцијски изрази који се извршавају одмах након стварања. Користе се функцијски изрази, а не декларације функција, зато што декларисана функција не може бити одмах позвана у истој наредби док функцијски израз може.
Пример. Следећа наредба ( function(){} )();
представља функцијски израз који се извршава одмах по креирању. █
Функцијски изрази који се одмах извршавају се користе за модуларно програмирање, јер сав код унутар овакве функције остаје приватан.
Коришћењем функцијских израза који се одмах извршавају може да се изабере које податке/функције ће да се задрже као приватни, а које податке/функције ће бити објављене. Јавно доступни ће бити они делови кода који се врате са резервисаном речи return
. На овај на начин се може у глобалном простору имена проследити све типове података.
Пример. Илуструје модуларно програмирање преко функцијских израза који се одмах извршавају. Наиме, потребно је извршити прорачунавање (у овом случају квадрирање) над датим аргументом. При томе, вредност аргумента је енкапсулирана у модулу, исто као и функција која врши прорачунавање.
У датотеци vrednost.js
је у посебан модул издвојено енкапсулирање вредности за podatakKojiSeCuva
. Сам модул је, преко функцијских израза који се одмах извршавају, изложио методе за приступ getPodatak()
и за промену setPodatak()
.
const vrednost = function () {
// ovo je privatan podatak
let podatakKojiSeCuva = '';
// funkcija za postavljanje vrednosti
function setPodatak(noviPodatak) {
podatakKojiSeCuva = noviPodatak;
}
// funkcija za ocitavanje vrednosti
function getPodatak() {
return podatakKojiSeCuva;
}
// publikovanje "javnih "funkcija
return {
setPodatak: setPodatak,
getPodatak: getPodatak
};
}();
У модулу који садржи датотека proracun.js
је изложена функција izracunajKvadrat()
.
const proracun = function () {
function izracunajKvadrat() {
// pozvan je metod iz vrednost.js
let x = vrednost.getPodatak();
// ovde ide deo koda vezan za proracun
return x * x;
}
// publikovanje "javne" funkcije
return {
izracunajKvadrat: izracunajKvadrat,
};
}();
Да би било обезбеђено учитавање модула, користиће се спољашњи АPI веб прегледача. Извршавање почиње од датотеке index.html
. Овде се, на почетку извршавања, потребно извршити учитавање модула. Редослед учитавања модула зависи од функционалности апликације, што је најтежи део за програмера наручито код великих и комплексних апликација. У овом примеру се прво учитава vrednost.js
јер се његове методе користе и у оквиру proracun.js
.
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Modularna aplikacija</title>
</head>
<body>
<!-- ucitavanje modula -->
<script src="vrednost.js" type="text/javascript"></script>
<script src="proracun.js" type="text/javascript"></script>
<!-- skripta koja koristi publikovane elemente modula -->
<script type="text/javascript">
let argument = 10;
vrednost.setPodatak(argument);
console.log(proracun.izracunajKvadrat());
</script>
</body>
</html>
Као што видимо, Дакле, учитавање и “регистрација” модула је реализовано извршавањем скрипти које садрже програмски код модула.
Кад се веб страна погледа у веб преглдачу, ако је све како треба, као резултат рада ће у конзоли прегледача бити уписан број 100
. █
Модули преко конструктора
Конструктор омогућава прављење више објеката на основу конструкторске функције.
Пример. Илуструје модуларно програмирање преко конструктора. И овде је задатак исти као у претходном примеру: потребно је извршити прорачунавање (квадрирање) над датим аргументом. При томе, вредност аргумента је енкапсулирана у модулу, исто као и функција која врши прорачунавање.
Измене кода у односу на претходни пример су мале - тело функција остаје непромењено а прилагођавају се следећи елементи:
-
како се ради о конструктору, то назив промењиве којој се додељује функција треба да почиње великим словом;
-
функцијски израз који се одмах извршава се трансформише у обичну функцију, брисањем заграда које одмах позивају функцију, јер се стандардна конструкторска функција позива са резервисаном речи
new
; -
унутар модула који позива методу из другог модула потребно је инстанцирати нови објекат, да би била доступна његова метода.
У датотеци vrednost.js
је у посебан модул издвојено енкапсулирање вредности за podatakKojiSeCuva
.
const Vrednost = function() {
// ovo je privatan podatak
let podatakKojiSeCuva = '';
// funkcija za postavljanje vrednosti
function setPodatak(noviPodatak) {
podatakKojiSeCuva = noviPodatak;
}
// funkcija za ocitavanje vrednosti
function getPodatak() {
return podatakKojiSeCuva;
}
// publikovanje "javnih "funkcija
return {
setPodatak: setPodatak,
getPodatak: getPodatak
};
};
У модулу који садржи датотека proracun.js
је преко конструктора изложена функција izracunajKvadrat()
.
const Proracun = function () {
function izracunajKvadrat() {
// pozvan je metod iz vrednost.js
let x = vrednost.getPodatak();
// ovde ide deo koda vezan za proracun
return x * x;
}
// publikovanje "javne" funkcije
return {
izracunajKvadrat: izracunajKvadrat,
};
};
Као и у претходном примеру, да би било обезбеђено учитавање модула, користиће се спољашњи АPI веб прегледача. И овдe извршавање почиње од датотеке index.html
. И у овом примеру је редослед учитавања модула експлицитно дат редоследом <script>
елемената: прво учитава vrednost.js
, па потом proracun.js
.
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Modularna aplikacija</title>
</head>
<body>
<!-- ucitavanje modula -->
<script src="vrednost.js" type="text/javascript"></script>
<script src="proracun.js" type="text/javascript"></script>
<!-- skripta koja koristi publikovane elemente modula -->
<script type="text/javascript">
let argument = 10;
let vrednost = new Vrednost();
vrednost.setPodatak(argument);
let proracun = new Proracun();
console.log(proracun.izracunajKvadrat());
</script>
</body>
</html>
Као резултат рада ЈаваСкрипт модуларног кода, у конзоли прегледача ће бити уписан број 100
. Исто као у претходном примеру, евентуална промена редоследа учитавања тј. промена редоследа <script>
елемената би довела до грешке у извршавању. █
ES5 модули преко спољних библиотека
Управо због недостатка подршке за модуле у ES5, креиране су спољашње синтаксе које језику дају недостајућу функционалност:
-
Асинхрона дефиниција модула (енгл. Asynchronous Module Definition - AMD)
-
CommonJS модули
-
Универзална дефиниција модула (енгл. Universal modul definition - UMD)
Асинхрона дефиниција модула, као што јој и само име каже, подржава асихроно учитавање модула што је погодно за рад са модулима у прегледачу.
CommonJS учитава модуле синхроно и због тога се најчешће користи за рад са модулима на серверској страни у окружењу node.js. Иако није планиран за рад наклијентској страни, тј. унутар прегледача, уз помоћ алата за увезивање модула је могуће прилагодити CommonJS раду у веб прегледачу.
Универзална дефиниција модула је компатибилна и са AMD и са CommonJS дефиницијом и користи се углавном уколико има потребе да се исти модул учитава на серверу и у веб прегледачу.
Асинхрона дефиниција модула
Под AMD синтаксом се подразумева договорени скуп правила и спецификација које указују како треба да изгледа код за креирање модула.
Основа AMD синтаксе је функција define()
, која преко прослеђених аргумената дефинише сам модул и зависности од других модула.
Функција define()
има три параметра:
id
– ниска која представља назив модула (без еxтензије) - овај параметар је опцион, тј. није обавезан.dependencies
– низ ниски са називима потребних модула или релативних путања до свих модула који су потребни за успешан рад датог модула. АКо је овај низ празан, то значи да модул не зависи од других модула. Редослед у низу је битан јер дефинише редослед учитавања.factory
– функција повратног позива која креира објекат-модул. Уколико модул зависи од других модула, онда ти модули од којих овај зависи морају бити параметри ове функције. Она вредност (функцију, објекат, итд) који функција за креирање врати као резултат ће бити вредност коју модул извози - тј. излаже спољном свету на коришћење.
Треба истаћи да је имплементација асинхорне дефиниције модула могућа тек уз помоћ алати за учитавање модула, па је поред коришћења саме синтаксе потребно учитати одговарајући алат за учитавње модула - овде ће се за ту сврху користити require.js.
У случају када се као алат за учитавње модула користи require.js библиотека, програмеру је на располагању функција requirejs()
, која омогућава извршавање програмског кода који зависи од модула. Функција requirejs()
има два параметра:
- низ ниски који садржи информације (имена или путање) о модулима од којих дати код зависи
- функцију повратног позива која ће бити извршена када буду учитани сви модули из претходног низа (наравно, ако неки од тих модула зависи од других, онда ће ти други модули бити учитани пре зависног).
Пример. Илуструје модуларно програмирање преко асинхроне дефиниције модула. Опет је задатак исти као у претходним примерима: потребно је извршити прорачунавање (квадрирање) над датим аргументом. При томе, вредност аргумента треба енкапсулирана у модулу, а исто тако и функција која врши израчунавање.
С обзиром да ће полазна тачка извршавања ЈаваСкрипт кода бити веб страна, онда је библиотека require.js
довучена и смештена у исти директоријум у ком се налазе све остале датотеке, а алат за учитавање модула бива покренут коришћењем елемента script
са атрибутом src
који има вредност путање до те библиотеке и са атрибутом data-main
који има вредност путање до ЈаваСкрипт датотеке од које почиње учитавање модула. За разлику од претходних случајева, овде процес учитавања реализује библиотека require.js
, па нема потребе да програмер ручно подешава редослед учитавања модула, нити може доћи до грешке зато што је тај редослед погрешан.
Mодул vrednost.js
не зависи од других модула, тако да аргумент задужен за назив модула (или релативну путању до модула) остаје празан:
define([], function() {
// ovo je privatan podatak
let podatakKojiSeCuva = '';
// funkcija za postavljanje vrednosti
function setPodatak(noviPodatak) {
podatakKojiSeCuva = noviPodatak;
}
// funkcija za ocitavanje vrednosti
function getPodatak() {
return podatakKojiSeCuva;
}
// publikovanje "javnih "funkcija
return {
setPodatak: setPodatak,
getPodatak: getPodatak
};
});
Mодулу proracun.js
је потребам модул vrednost,js
, па је стога додата релативна путања до тог модула у низ. Поред тога, убачени модул je прослеђен као аргумент функције:
define(['./vrednost'], function (vrednost) {
function izracunajKvadrat() {
// pozvan je metod iz vrednost.js
let x = vrednost.getPodatak();
// ovde ide deo koda vezan za proracun
return x * x;
}
// publikovanje "javne" funkcije
return {
izracunajKvadratAMD: izracunajKvadrat
};
});
У горњем модулу је одлучено да према спољашњости буде изложена функција izracunajKvadrat()
, при чему ће јој се из спошашњости приступати преко имена izracunajKvadratAMD()
.
Mодул index.js
зависи и од модула vrednost,js
и од модула proracun.js
, па су аргумент који садржи низ зависности додате путање до оба модула и функција повратног позива има два аргумента:
define(['./vrednost', './proracun'], function (vrednost, proracun) {
function pokreni() {
let argument = 10;
vrednost.setPodatak(argument);
console.log(proracun.izracunajKvadratAMD());
};
// publikovanje "javne" funkcije
return {
pokreniAMD: pokreni,
};
});
У претходном скрипту је, дакле, нанправљен модул, у њему направљена функција pokreni()
и та функција је изложена за коришћење под именом pokreniAMD()
.
Извршавање програмског кода почиње од датотеке index.html
. Сада, уместо више узастопно учитаних скрипти, имамо само једну која учитава require.js
, а он брине о учитавању свих осталих модула. Обезбеђивање да се учитају зависни модули пре извршења дате ЈаваСкрипт функције, постигнуто је помоћу функције requrejs()
:
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Modularna aplikacija</title>
</head>
<body>
<!-- ucitavanje modula -->
<script data-main="index.js" src="./require.js"></script>
<!-- skripta koja koristi publikovane elemente modula -->
<script type="text/javascript">
// poziv publikovane funkcije
requirejs(["index"], function (index) {
index.pokreniAMD();
});
</script>
</body>
</html>
Ако је све како треба, приликом прегледа ове веб стране у конзоли прегледача ће бити уписан број 100
. █
AMD синтакса решава највеће недостатке које у раду са модулима исказује нативни ES5 приступ:
-
овде је уграђен менаџмент модула у склопу алата за учитавање који замењује “ручно” одређивање редоследа учитавања
-
глобални простор имена је “загађен” само са једним именом
require
уместо са свим називима модула.
Међутим, и овај приступ има одређене мане:
-
код функције
define()
листа зависности у низу мора да се слаже са листом аргумената прослеђених функцији, што може да буде тешко када има пуно зависности. -
синтакса захтева да је цео код обавијен са
define()
функцијом, што захтева додатно увлачење кода. -
у случају коришћења алата за учитавање модула и протокола HTTP 1.1, при учитавању великог броја пуно малих ЈаваСкрипт даотека може да се јави проблем са перфомансама (ово може да се превазиђе коришћењем HTTP/2 протокола или коришћењем алата за увезивање модула)
Сматра се да је уз AMD боље користити алате за учитавање него алате за увезивање, јер тада асинхрони рад долази до изражаја и показује свој пун потенцијал. Наиме, при учитавању се довлаче само потребни модули, док се при увезивању довлачи једна велика датотека који обихвата све модуле.
CommonJS модули
CommonJS синтакса је првенствено планирана за коришћење на серверу, за рад у синхроном моду. За рад на серверу је логичан избор коришћење алата за учитавање модула SystemJS
.
Уколико има потребе да се CommonJS синтаксу користи у окружењу веб прегледача, потребно је да се користи алате за увезивање (browserify или webpack) који може да прилагоди ову синтаксу раду у прегледачу. За разлику од AMD где тело модула треба да буде обавијено функцијом, код CommonJS нема никаквог омотача јер се сматра да је свака ЈаваСкрипт датотека један модул.
Извоз метода се може извршити на два начина:
- додељивањем сваке методе појединачно објекту
module.export
:
module.export.imeMetodeZaSpoljasnost1 = imeMetodeIzModula1
module.export.imeMetodeZaSpoljasnost2 = imeMetodeIzModula2
- додељивањем објекта са жељеним методаaм објекту
module.export
:
module.export = {
imeMetodeZaSpoljasnost1 : imeMetodeIzModula1,
imeMetodeZaSpoljasnost2 : imeMetodeIzModula2
}
Увоз модула, тј. коришћење метода из других модула се омогућава дефинисањем нове промењиве и доделом њене вредности користећи функцију require()
. Функција require()
за параметар има ниску - релативну путању до модула који се захтева, а као резулата враће референцу на захтевани модул.
var promenjivaKojaReferiseNaDrugiModul = require('./drugiModul');
Важно је истаћи да је увезени модул само копија извезене вредности, тј. копија модула. Копија враћена са require()
је прекинула везу са оригиналом.
Пример. Илуструје модуларно програмирање преко CommonJS. Опет је задатак исти као у претходним примерима: потребно је извршити прорачунавање (квадрирање) над датим аргументом. При томе, вредност аргумента треба енкапсулирана у модулу, а исто тако и функција која врши израчунавање. Међутим, за разлику од ранијих ситуација, у овом примеру се код извршава на серверској страни.
Модул vrednost.js
извози функције getPodatak()
и setPodatak()
, а вредност променљиве podatakKojiSeCuva
остаје сакривена:
// ovo je privatan podatak
let podatakKojiSeCuva = '';
// funkcija za postavljanje vrednosti
function setPodatak(noviPodatak) {
podatakKojiSeCuva = noviPodatak;
}
// funkcija za ocitavanje vrednosti
function getPodatak() {
return podatakKojiSeCuva;
}
// publikovanje "javnih "funkcija
exports.setPodatak = setPodatak;
exports.getPodatak = getPodatak;
Модул proracun.js
захтева функционалност модула vrednost.js
на који реферише променљива vrednost
, користи функцију getPodatak()
из тог модула, те креира и ивози функцију за квадрирање, под именом izracunajKvadratCommonJS()
:
const vrednost = require("./vrednost");
function izracunajKvadrat() {
// pozvan je metod iz vrednost.js
let x = vrednost.getPodatak();
// ovde ide deo koda vezan za proracun
return x * x;
}
exports.izracunajKvadratCommonJS = izracunajKvadrat;
Датотека index.js
је улазна тачка за извршење програма:
const vrednost = require("./vrednost");
const proracun = require("./proracun");
const argument = 10;
vrednost.setPodatak(argument);
console.log(proracun.izracunajKvadratCommonJS());
Приликом извршавања овог скрипта у окружењу node
, на конзоли терминала ће бити прикан број 100
. █
CommonJS приступ као и AMD решава проблем “ручног” управљања редоследом учитавања модула и смањује “загађење” глобалног опсега дефинисаности и то са још једноставнијом синтаксом него код AMD.
Међутим, овај приступ има и својих мана:
- синхрони рад није баш најбољи за прегледач и у том контексту је обавезно је коришћење алата за увезивање
- сваки модул је потребно сместити у једну одвојену датотеку
- не подржава цикличне зависности
- CommonJS има динамичку структуру модула, која се дефинише тек у време извршавања кода, па у неким случајевима није лако испратити шта се уствари извози све док се не изврши код.
ES6 модули
Уз ЕS6 стиже подршка за модуларни систем уграђена и у сам језик – ECMAScript 6 модули. Тиме је интегрисана подршка за модуларно програмирање:
- Овај приступ подржава модуле који се смештају у датотеке – један модул по датотеци.
- Модули су уникати, или јединствени примерци, или синглотни (енгл. singleton) - сваки модул се извршава само једном.
- ЕS6 модули могу да раде и синхроно и асихроно.
- Подржана је циклична зависност модула.
- Синтакса је још компактнија од CommonJS.
ЕS6 модули имају статичку структуру. Пошто је структура модула непромењива, обично је довољно да се прегледа код да би се схватило шта се где увози. Ово није случај код динамичке структуре која карактерише CommonJS, где је често потребно да се програмски код изврши да би се видело шта се увози. Стога, код овог приступа, евентуалне грешке могу да се открију и у време prevo]ewa (са алатом за увезивање модула), јер се све радње које се односе на увоз и извоз модула одређују у тренутку увезивања модула.
Сви увезени елементи су непромењиви из модула који их је увезао. Свака операција доделе вредности увеженом елементу би проузроковала грешку (изузетак типа TypeError
).
ЕS6 се не прави копију својства већ дели везу на то својство. Ово важи чак и за дељење примитивних својстава (својстава која су типа број или ниска).
Стандардни извоз се постиже помоћу резервисане речи export
- њеним постављањем испред декларације променљиве/функције.
Пример. Илуструје станардни извоз.
export var foo = 1;
export var foo = function () {};
export var bar;
export let foo = 2;
export let bar;
export const foo = 3;
export function foo () {}
export class foo {}
█
Именовани извоз је врста извоза којом на крају модула дефинишу елементе који се извозе. Назив је потекао из чињенице да се за дефинисање елемената који се извозе користе њихова имена. Неком извезеном елементу се може дати друго име коришћењем кључне речи as
.
Пример. Илуструје именовани извоз.
export {};
export {foo};
export {foo, bar};
export {foo as bar};
█
Препорука је да се при извозу неког модула дефинише део модула који се подразумевано извози. Подразумевани извоз се дефинише са кључном речи default
. У једном модулу је дозвољен само један подразумевани извоз. Извезени део се у другом модулу користи под именом default
.
Пример. Илуструје подразумевани извоз.
export default 42;
export default {};
export default [];
export default (1 + 2);
export default foo;
export default function () {}
export default class {}
export default function foo () {}
export default class foo {}
█
Подразумевани извоз може да се комбинује са именованмм извозом уз коришћење кључне речи as
.
Пример. Илуструје комбиновање подразумеваног и именованог извоза.
export {foo as default};
export {foo as default, bar};
█
Уколико модул има приличан број “разбацаних” елемената који се извозе, препорука је да се на врху модула јасно и прегледено дефинише API као што следи:
Пример. Илуструје извоз API-ја.
// calculator.js
const api = { add, subtract, multiply, divide };
function add(a,b) {...}
function subtract(a,b) {...}
function m ultiply(a,b) {...}
function divide(a,b) {...}
function somePrivateHelper() {...}
export default api;
█
Увоз модула се реализује коришћењем резервисане речи import
, иза које следи списак функија које се увозе (евентуално и њихових алијса), затим кључна реч from
, па ниска која садржи путању до датотеке са модулом.
Пример. Илуструје увоз функција из модула.
import { foo } from "foo";
█
Пример. Илуструје именовани увоз функција из модула.
import {} from "foo";
import {bar} from "foo";
import {bar, baz} from "foo";
import {bar as baz} from "foo";
import {bar as baz, xyz} from "foo";
█
Увоз подразумеваних вредности се врши једноставним позивањем имена модула или позивањем преко имена.
Пример. Илуструје подразумевани увоз из модула.
import foo from "foo";
import {default as foo} from "foo";
█
Увоз целог простора имена модула, још се зове и глобални увоз, се користи уколико треба да се у једној наредби учитају сви извезени елементи једног модула. Овакав глоблани увоз косити синтаксу * as
.
Пример. Илуструје подразумевани увоз из модула.
import * as bar from "foo"
Након оваквог глобалног увоза доступни су сви елементи из модула foo
у модулу под “новим” именом bar
:
bar.x;
bar.baz();
█
Увоз модула има следеће карактеристике:
-
Декларација промењиве или функције се у фази превођења диже на почетак (врх) области дефинисаности.
-
Сви увезени елементи су непромењиви из модулуа који врши увоз. Свака операција додељивања вредности за увезени елемент у оквиру модула (који је увезао) би проузроковала грешку типа
TypeError
. -
Није дозвољено коришћење промењивих у наредби
import
. ES6 mодули су статични па, извршење наредбеimport
не сме да зависи од нечега што је добијено у време извршења. -
Условно учитавање модула само са ES6 синтаксом није могуће. Разлог за поменуто је то што
import
наредба увек мора бити на најспољашњијем нивоу, па се не може наћи у блоку, самим тим ни у телу наредбе гранања.
Пример. Илуструје ефекат дизања променљиве приликом увоза.
Због дизања увезене функције, позивање те функције неће избацивати грешку:
foo();
import { foo } from "foo";
█
Нова ES6 синтакса још увек није подржана од стране свих прегледача, па је потребно користи алате (нпр. Babel) да би се транспилирала у ES5. Тај сценарио је прихватљив и за извршавање у оквиру прегледача и на серверској страни, само што захтева инсталацију додатних библиотека.
Поред тога, могуће је извршити ЕS6 модуларни код и на платформи node
без инсталације додатних библиотека. За успешно покретање ЕS6 модуларног кода на node
платформи и без додатних бибблиотека-транспилатора (у овом тренутку) потребно је да:
- верзија окружења
node
буде 12 или више - датотеке које садрже такав код имају екстензију
mjs
(а неjs
као што је то досад био случај) - извршавање кода покреће са опцијом
--experimental-modules
Пример. Илуструје модуларно програмирање преко ES6. Потребно је извршити прорачунавање (квадрирање) над датим аргументом. При томе, вредност аргумента треба енкапсулирана у модулу, а исто тако и функција која врши израчунавање. У овом примеру ће се порграмски код извршавати на серверској страни.
Модул vrednost.mjs
коришћењем наредбе export
извози функције getPodatak()
и setPodatak()
:
// ovo je privatan podatak
let podatakKojiSeCuva = '';
const _setPodatak = function (noviPodatak) {
podatakKojiSeCuva = noviPodatak;
};
export { _setPodatak as setPodatak };
const _getPodatak = function() {
return podatakKojiSeCuva;
};
export { _getPodatak as getPodatak };
Модул proracun.mjs
увози функцију getPodatak()
из модула vrednost.mjs
те креира и ивози функцију за квадрирање, под именом izracunajKvadratES6()
:
import { getPodatak } from "./vrednost";
function izracunajKvadrat() {
// pozvan je metod iz vrednost.js
let x = getPodatak();
// ovde ide deo koda vezan za proracun
return x * x;
}
export const izracunajKvadratES6 = izracunajKvadrat;
Датотека index.mjs
је улазна тачка за извршење програма:
import { setPodatak } from "./vrednost";
import { izracunajKvadratES6 } from "./proracun";
let argument = 10;
setPodatak(argument);
console.log(izracunajKvadratES6());
Ту се увози функција setPodatak()
из модула vrednost.mjs
и функција izracunajKvadratES6()
из модула proracun.mjs
, а потом се те две увезене функције користе за постављање вредности аргумента и за израчуб+навање.
Покретањем скрипте уз ддодатне параметре у node
окружењу, тј. изршењем наредбе:
node --experimental-modules index.mjs
добија се следећи резултат:
(node:14480) ExperimentalWarning: The ESM module loader is experimental.
100
Дакле, као и у свим претходним ситуацијама, на конзоли се приказује број 100
. █
Алати за учитавање и за увезивање модула
Уколико се програмер одлучи за модуларни начин програмирања апликације, он ће се сусрести са проблемом организовања учитавања потребних модула, као и модула од којих су они зависни (енгл. dependinces). Алати за учитавање (енгл. modul loader) и за увезивање модула (енгл. modul bundler) су непоходна подршка програмеру за једноставнији и ефикаснији рад са модулима.
Алати за учитавање модула
Алати за учитавање модула омогућавају динамичко учитавање модула водећи рачуна о распореду учитавања, како би се зависни модули раније учитали. Најпознатији алати за учитавање модула су:
-
RequireJS – имплементира AMD модуларни систем.
-
SystemJS – је универзални алат за учитавање, који може учитавати модуле у било ком популарном формату (CommonjS, UMD, AMD, ES6).
Алати за увезивање модула
Алати за увезивање модула решавају проблем тако што компајлирају све модуле у једну датотеку према одређеном реду, водећи рачуна о томе да неки модул од којег зависи други буде учитан на време. Најпознатији алати за увезивање модула:
-
Browserify – имплементира CommonjS и у окружењу веб прегледача. Може да се надограшђује разним додацима и уз помоћ извршилаца задатака (енгл. task runner) (најчешће су то алати Gulp или Grunt) може да изврши различите послове.
-
Webpack – може учитавати модуле у било ком популарном формату (CommonjS, UMD, AMD, ES6), долази као један пакет и нису му потребни додатци. Поред учитавања модула може да изврши посао транспиловања ES6 у ES5, као и транспиловање SASS у CSS.
Литература
-
Haverbeke M.: Eloquent JavaScript
-
JavaScript - Mozzila Developer Network (MDN)
-
Живановић, Д.: Веб програмирање - ЈаваСкрипт
-
Copes F.: Complete JavaScript Handbook