A wrapper function (Node.js – 1.)

A kódbazis.hu (Bolgár Máté) Node.js tutorial alapján.

A Node.js egy futtató környezet, ami lehetőséget nyújt JavaScriptben írt programok futtatására. JavaScriptet leginkább kliens oldalon szoktak használni a böngészőn keresztül. A Google Chrome böngésző JavaScriptet futtató motorja a V8 engine, aminek a fejlesztése 2006-ban kezdődött. Neve játékos utalás a V8 benzinmotorok teljesítményére (a V8 motorokban 8 henger van két sorban elhelyezve, melyek ‘V’ betűt formázó szöget zárnak be). A Node.js is ezt a motort használja némi módosítással, továbbá beépített könyvtárakat tartalmaz. Alapvetően webszerverek készítésére hozták létre.

Nincs GUI-ja, csak CLI-ből használható. Használata adott mappán megnyitott parancssorból vagy VS Code termináljából: készítünk egy pl. index.js fájlt, majd beírjuk a parancssorba, hogy node index.js, akkor a node lefuttatja az index.js fájlt.

A wrapper function

A a parancssorban a node paranccsal lefuttatott kód tkp. egy wrapper function-be van csomagolva, azaz mintha a kód körül lenne véve ezzel a wrapper function-nel, s futtatáskor ez hívódna meg. Ez a function kap paramétereket, amelyeket a forráskódban tudunk használni. Ezek: __dirname, __filename; module, require, exports, ezeket ki is lehet logolni.

  • __dirname: a forrásfájl operációs rendszeren belüli abszolút útvonala
  • __filename: ugyanez, csak még hozzáfűzve a fájl neve
  • module, require, exports: fájlok között funkcionalitás megosztása; ha van egy másik fájlunk, s abban egy function, amit szeretnénk megosztani, akkor a module object export kulcsa alá kell bekötni a megosztani kívánt adatot, ami leginkább object vagy function szokott lenni. Pl.: a fájl neve legyen calculator.js.
function add(num1,num2){
     return num1+num2
}

module.exports = add
  • require: Innentől kezdve ez a function elérhető a többi fájl részére is. Abban a fájlban, ahova meg akarjuk hívni a függvényt, ott a require() függvényben kell megadni a behúzni kívánt fájl relatív útvonalát. A fájlkiterjesztés JavaScript fájlok esetén (is) elhagyható, ill. egészen pontosan, ha nem adunk meg fájlkiterjesztést, akkor a node.js először a .js, majd a .json, majd a .node fájlok között fogja keresni az adott fájlt. Ez keveredést okozhat, így jobb odaírni a fájlkiterjesztéseket.
const add = require('./calculator.js')
console.log(add(4,5)  //9
  • require.resolve: szerver oldalon gyakrabban van szükség egy modul elérési útvonalára. Ez az utasítás nem húzza be a függvényt, így azt nem tudjuk használni, csak a függvény elérési útvonalával tér vissza. Pl.:
const add = require.resolve('./calculator.js');
console.log(add) //C:\Users\Kevin\Desktop\new_map\calculator.js

A Node.js-ben tehát a module.exports utasítással exportálunk és a require utasítással importálunk. Egy fájlból ezen a módon csak egy adatot tudunk exportálni, s importáláskor az az adat lesz behúzva. Egy fájlon belül több module.exports=… utasítás esetén az utolsó lesz érvényes. Hogy hogyan lehet másként egy modulból több adatot exportálni, arra később visszatérünk.

Ez a Node.js esetén rákényszerít a modular code technikára, azaz az egy függvény – egy modul eljárásra.

Viszont a require függvény és a module object a Node.js-en belül globális scope-pal rendelkezik, így nincs szükség a require(‘require’), ill. a require(‘module’) utasításokra.

Az exports és a module.exports

Az adatok exportálása tehát a module.export utasítással történik, ami tkp. a module object egy export nevű kulcsa, s ez alá írunk be valamit. Ugyanakkor a Node.js-en belül az export egy olyan object, amelyet deklarálás nélkül használhatunk. Pl. deklarálás nélkül beírhatjuk egy fájlba:

exports.kulcs="hello"
console.log(exports.kulcs)  // hello

Azaz a wrapper function exports paramétere és a modules.export két különböző dolog!

Mi a module?

[Irodalom: developpaper.com] Írok két JS fájlt, az index1.js-et és az index2.js-t, s mindkettőt a Desktopon helyezem el.

//index1.js

function add(num1,num2){
console.log(num1,num2);
return num1+num2};
module.exports=add;
console.log(module);
//index2.js

add = require('./index1.js');
console.log(add(2,5));
console.log(module);

Az index1.js module-ja ez lesz:

C:\Users\Kevin\Desktop>node index1.js
 Module {
  id: '.',
  path: 'C:\Users\Kevin\Desktop',
  exports: [Function: add],
  parent: null,
  filename: 'C:\Users\Kevin\Desktop\index1.js',
  loaded: false,
  children: [],
  paths: [
    'C:\Users\Kevin\Desktop\node_modules',
    'C:\Users\Kevin\node_modules',
    'C:\Users\node_modules',
    'C:\node_modules'
 ]
}

Az index2.js module-ja ez lesz:

Module {
 id: '.',
 path: 'C:\Users\Kevin\Desktop',
 exports: {},
 parent: null,
 filename: 'C:\Users\Kevin\Desktop\index2.js',
 loaded: false,
 children: [
   Module {
     id: 'C:\Users\Kevin\Desktop\index1.js',
     path: 'C:\Users\Kevin\Desktop',
     exports: [Function: add],
     parent: [Circular *1],
     filename: 'C:\Users\Kevin\Desktop\index1.js',
     loaded: true,
     children: [],
     paths: [Array]
   }
 ],
paths: [
 'C:\Users\Kevin\Desktop\node_modules',
 'C:\Users\Kevin\node_modules',
 'C:\Users\node_modules',
 'C:\node_modules'
 ]
}
  • id attributum: a fájl abszolút elérési útvonala, ennek az alapján azonosítja a node.js a fájlt.
  • paths attributum: láthatóan egy halom – létező és nem létező – abszlút útvoval az operációs rendszerel belül. Az index2.js a require függvénnyel behúzza az index1.js-t, amelynek az elérési útvonalát adjuk meg a függvény paramétereként: add = require(‘./index1.js’). Ehelyett írhatjuk ezt is: add = require(‘index1.js’), azaz itt csak a file nevét adtuk meg, nem adtunk hozzá elérési útvonalat. Ekkor a node.js a paths attributum alatt megadott elérési útvonalakon próbálja megtalálni a fájlunkat. S így már nem fogja megtalálni, mivel a C:\Users\Kevin\Desktop nem szerepel az útvonalak között. De ha készítünk egy node_modules mappát a Desktopon, s abba helyezzük el az index1.js fájlt, akkor meg fogja találni, mivel a C:\Users\Kevin\Desktop\node_modules szerepel a paths útvonalai között.
  • children, parent: az index2.js a require eljárással behúzza az index1.js-t, így az szerepel a children tömbjében. Az már nem húz be semmit, ezért annak nincs gyereke. A parent-nál viszont azt látjuk, hogy [Circular *1], mivel itt egy körkörös – önmagába visszatérő – függőségről van szó, hiszen az index1.js-t az index1.js-ből húztuk be, így saját maga a szülöje.
  • export: a module object export kulcsa alá kerül be az, amit kiexportáltunk a fájlból.
  • loaded: jelzi, hogy a module ba van-e töltve. Akkor lesz az értéke true, ha a module teljesen be lett töltve. Így a fentiek mellett a node index2.js utasításra a kiírt module-ban azt látjuk, hogy az index2.js-nál ez false, mivel a kiíratáskor a fájl még fut, de az index1.js-re true, mivel a kiíratáskor az már be lett töltve a require utasítással mindjárt a fájl elején.

A kód futtatása a következőképpen történik (kép forrása):

  • resolving: a célmodul megtalálása és az abszolút út generálása
  • loading: annak meghatározása, hogy a modul JS, JSON vagy Node fájl-e
  • wrapping: becsomagolás privát scope-ba
  • evaluating: futtatás
  • caching: cache-elés, hogy későbbi használatnál ne kelljen a fenti lépéseket megismételni

Wrapping

A node.js tehát nem direkt módon futtatja az adott js kódot, hanem előtte becsomagolja a kódot egy wrapper függvénybe. Ez a function így néz ki (nodejs.org dokumentációja):

(function (exports, require, module, __filename, __dirname) {
  //module code
});

Ezzel a következő dolgok valósulnak meg:

  • Mivel itt függvény scope-ról van szó, így a modulon belül használt var, let, const kulcsszavakkal deklarált változók scope-ja a modul lesz, így azok nem lesznek globálisan elérhető változók.
  • Minden modulnak megvan a saját wrapper függvénye, s a __dirname, __filename; module, require, exports változók minden modul esetében lokálisak.

Az index2.js fájlon belül console.log(arguments) utasítással kiírathatjuk a wrapper function paramétereit:

[Arguments] {
 '0': {},
 '1': [Function: require] {
   resolve: [Function: resolve] { paths: [Function: paths] },
   main: Module {
     id: '.',
     path: 'C:\Users\Kevin\Desktop',
     exports: {},
     parent: null,
     filename: 'C:\Users\Kevin\Desktop\index2.js',
     loaded: false,
     children: [Array],
     paths: [Array]
   },
   extensions: [Object: null prototype] {
     '.js': [Function (anonymous)],
     '.json': [Function (anonymous)],
     '.node': [Function (anonymous)]
   },
   cache: [Object: null prototype] {
     'C:\Users\Kevin\Desktop\index2.js': [Module],
     'C:\Users\Kevin\Desktop\node_modules\index1.js': [Module]
   }
 },
 '2': Module {
   id: '.',
   path: 'C:\Users\Kevin\Desktop',
   exports: {},
   parent: null,
   filename: 'C:\Users\Kevin\Desktop\index2.js',
   loaded: false,
   children: [ [Module] ],
   paths: [
     'C:\Users\Kevin\Desktop\node_modules',
     'C:\Users\Kevin\node_modules',
     'C:\Users\node_modules',
     'C:\node_modules'
   ]
 },
 '3': 'C:\Users\Kevin\Desktop\index2.js',
 '4': 'C:\Users\Kevin\Desktop'
}

Az exportálás három módja

(Irodalom: Srishti Gupta – medium.com) A legegyszerűbb módra fenntebb láttunk példát:

function add(num1,num2){
return num1+num2
}

module.exports=add

A module.export tkp így néz ki (kép forrása, mondjuk a helyesírás-ellenőrőt kikapcsolhatta volnaSrishti Gupta):

S mivel a module.export eredetileg egy object, ezért használhatunk rajta kulcsokat, azaz különböző kulcsok alatt különböző adatokat adhatunk meg, majd a require utasítással ezt az object-et kapjuk meg, aminek hivatkozhatunk a kulcsaira.

//index1.js

const add = function(num1,num2){
console.log(num1+num2);
return num1+num2};
module.exports.add=add;

function mult(num1,num2){
console.log(num1num2);
return num1num2};
module.exports.mult=mult;

//index2.js

const add = require('./index1.js').add;
add(2,5)
const mult = require('./index1.js').mult;
mult(2,5)

//7
//10

A másik megoldás ugyanerre épül, csak ott az object-et máshogy adjuk meg:

const add = function(num1,num2){
console.log(num1+num2);
return num1+num2}

function mult(num1,num2){
console.log(num1num2);
return num1num2}

module.exports = {add:add, mult:mult}

(Még tömörebben az uolsó sor: module.exports = {add, mult}

require.cache

Egy fájlon belül a console.log(require) utasítással kiírathatjuk a require objectumot. Ekkor az objectumon belül látni fogunk egy chache kulcsot, ami alatt az eddig betöltött modulok lesznek eltárolva, s így az ismételt modulhívás esetén tkp. csak cache-elés történik.