Tudnod kell, hogy a Javascript (jobban mondva az EcmaScript) nem ad meg semmilyen függvényt a fájlok írására és olvasására.

A Javascript valójában csak a nyelv, amelyet számos olyan környezet használ (a böngésző, vagy a NodeJS példák a környezetekre), amelyek több objektumot és funkciót kínálnak a munkához.

A Node volt az első környezet, amely módot kínált a kód modulokba szervezésére egy speciális, require() nevű függvény segítségével. Hogyan működik? Próbáljuk meg a nulláról megvalósítani.

Itt egy példa a require működésére:

//test.jsmodule.exports = "Hello World";
//main.jsconst test = require("./test.js"); console.log(test) 

Írjuk meg ezt a require függvényt.

Mit kell tennie egy require() függvénynek

Egy require függvénytől elvárják, hogy:

  • beolvassa egy javascript fájl tartalmát egy sztringben
  • kiértékeli ezt a kódot
  • a kivitt függvényt/objektumot egy gyorsítótárba menti későbbi használatra (csak egyszer olvassa be a fájlokat)

Disclaimer

Nem fogjuk az egész NodeJS-t egyetlen bejegyzésben újraépíteni. Valójában nem fogok sok NodeJS ellenőrzést és kacagást implementálni, minket csak az érdekel, hogy megértsük, hogyan működnek a dolgok.

Az igazi require függvényre továbbra is szükségünk lesz a fs modul betöltéséhez. Nem csalok, csak ennek a posztnak előbb-utóbb véget kell érnie 🙂

myRequire() függvény

Itt a kód:

//file setup.jsconst fs = require('fs');myRequire.cache = Object.create(null); //(1)function myRequire(name) { if (!(name in myRequire.cache)) { let code = fs.readFileSync(name, 'utf8'); //(2) let module = {exports: {}}; //(3) myRequire.cache = module; //(4) let wrapper = Function("require, exports, module", code); //(5) wrapper(myRequire, module.exports, module); //(6) } return myRequire.cache.exports; //(7)}...

Elfelejtetted deklarálni a myRequire változót?

Nem. A Javascriptben a function kulcsszóval deklarált függvények minden más kód előtt kiértékelődnek (a függvényeket “felemelik”), így már a deklarálásuk előtt is lehet rájuk hivatkozni.

A függvényeknek lehetnek tulajdonságaik is (ez a javascript!), így a myRequire függvényhez (1. lépés) hozzáadhatjuk a cache tulajdonságot.

Végül létrehozzuk a cache tulajdonságot a Object.create-vel. Ezzel a függvénnyel megadhatjuk az objektum prototípusát, mi úgy döntöttünk, hogy nem adunk meg prototípust. Hogy miért? Mert így nem keverjük össze a futásidő által deklarált más függvényeket vagy tulajdonságokat. Íme egy magyarázat.

Menjünk vissza a myRequire -hez. Ha az importálandó fájl nincs a gyorsítótárban, akkor beolvassuk a fájlt a lemezről (2. lépés).

Ezután deklarálunk egy üres module objektumot egyetlen tulajdonsággal, a exports-vel (3. lépés).

Ezt az üres modult hozzáadjuk a gyorsítótárhoz, a fájlnevet használjuk kulcsként, és máris megtörténik a varázslat (4. lépés).

A függvénykonstruktor

A JS-ben kétféleképpen értékelhetünk ki egy string js kódot. Az első mód a eval() függvényen keresztül, ez egy kicsit veszélyes (összezavarja a hatókörünket), ezért erősen ellenjavallt a használata.

A második módja a stringben lévő kódunk kiértékelésének a Function konstruktoron keresztül. Ez a konstruktor egy sztringet vesz fel az argumentumokkal és egy sztringet a kóddal. Így mindennek megvan a saját hatósugara, és nem rontja el a többiek dolgát.

Így lényegében egy új függvényt hozunk létre ezekkel a változókkal (5. lépés): require, exports és module. Gondoljunk egy pillanatra a bejegyzés első példájára, a test.js fájlra:

function(require, exports, module) { module.exports = "Hello World" }

és a második fájlra, main.js:

function(require, exports, module) { const test = require("./test.js"); console.log(test) }

A fájlokban “globálisnak” tűnő változókat valóban függvényargumentumként adjuk át.

Az utolsó lépés: a függvény végrehajtása

Elkészítettünk (6. lépés) egy wrapper változót, amely egy függvényt tartalmaz, de a függvény soha nem kerül végrehajtásra. Ezt a következő sorban tesszük:

wrapper(myRequire, module.exports, module); 

Megjegyezzük, hogy a második változó (amelynek exports-nek kellene lennie) csak egy fogantyú a module.exports-hoz; a NodeJS alkotói úgy gondolták, hogy ez segíthetett volna kevesebb kód írásában…

Amikor a Node végrehajtja a függvényt, minden, amit “exportált” (a nyilvános API-d) a cache-be kerül.

(Emlékszel a myRequire.cache = module; sorra? Amikor a fordító először találta meg, egy dummy { exports: {} } objektumra mutatott; most a te modulodat tartalmazza.)

MEGJEGYZÉS. Mivel a myRequire-t átadjuk a wrapper függvénynek, innentől kezdve használhatjuk a require-t a tesztfájljainkban, de a require-unk meghívást kap. Adj hozzá egy console.log-ot, ha nem bízol bennem 😉

Végre… myRequire visszaadja a exportdeklarált dolgokat (7. lépés), és amit elmentettünk a gyorsítótárba, így nem kell újra kiértékelnünk ezt a kódot.

Végső megfontolások

Egy példa erre a kódra itt található, néhány konzolnaplóval együtt, amelyek elmagyarázzák, mi történik.

A cikk ötlete a 10. fejezetben (Modulok) található magyarázatból származik. A könyv (Eloquent Javascript) kiváló, de késztetést éreztem arra, hogy jobban megértsem, és debuggerrel próbáljam ki azt, amit pusztán az eszemmel nem tudtam megérteni.

A könyvet mindenképpen el kell olvasnod, ha jobban meg akarod érteni a javascriptet.

  • Megosztás a Facebookon
  • Tweet
  • Megosztás a LinkedInen

Tagek: javascript – nodejs

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.