Sinun pitäisi tietää, että Javascript (parempi sanoa EcmaScript) ei määritä mitään funktiota tiedostojen lukemiseen ja kirjoittamiseen.
Tosiasiassa Javascript on vain kieli, jota käyttävät monet ympäristöt (selain tai NodeJS ovat esimerkkejä ympäristöistä), jotka tarjoavat enemmän objekteja ja funktioita työskentelyyn.
Node oli ensimmäinen ympäristö, joka tarjosi tavan järjestää koodia moduuleihin käyttämällä erityistä funktiota nimeltä require()
. Miten se toimii? Yritetään toteuttaa se nollasta alkaen.
Tässä on esimerkki require
:n toiminnasta:
//test.jsmodule.exports = "Hello World";
//main.jsconst test = require("./test.js"); console.log(test)
Kirjoitetaan tuo require
-funktio.
Mitä require()-funktion pitäisi tehdä
Funktion require
odotetaan:
- lukee javascript-tiedoston sisällön merkkijonona
- arvioi tuon koodin
- tallentaa viedyn funktion/objektin välimuistiin myöhempää käyttöä varten (lukee tiedostot vain kerran)
Disclaimer
Emmekä aio rakentaa koko NodeJS:ää uudestaan yhdessä postauksessa. Itse asiassa en tule toteuttamaan monia NodeJS:n tarkistuksia ja kikatuksia, meitä kiinnostaa vain ymmärtää miten asiat toimivat.
Tarvitsemme edelleen oikeaa require
-funktiota lataamaan fs
-moduulin. En huijaa, tämän postauksen on vain loputtava ennemmin tai myöhemmin 🙂
myRequire()-funktio
Tässä on koodi:
//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)}...
Unohditko julistaa myRequire-muuttujan?
Ei. Javascriptissä function
-avainsanalla ilmoitetut funktiot evaluoidaan ennen muuta koodia (funktiot ”nostetaan”), joten niihin voidaan viitata jo ennen kuin ne on ilmoitettu.
Funktioilla voi myös olla ominaisuuksia (tämä on Javascript!), joten voit lisätä cache
-ominaisuuden myRequire
-funktioon (vaihe 1).
Luotaan cache
-ominaisuus Object.create
:llä. Tällä funktiolla voimme määrittää objektin prototyypin, olemme päättäneet olla määrittelemättä prototyyppiä. Miksi? Näin emme sotkeudu muihin funktioihin tai ominaisuuksiin, jotka runtime on julistanut. Tässä on selitys.
Palaamme takaisin kohtaan myRequire
. Jos tuomamme tiedosto ei ole välimuistissa, luemme tiedoston levyltä (vaihe 2).
Sitten julistamme tyhjän module
-olion, jolla on vain yksi ominaisuus, exports
(vaihe 3).
Lisäämme tämän tyhjän moduulin välimuistiin käyttäen tiedostonimeä avaimena, ja sitten taika tapahtuu (vaihe 4).
Funktion konstruktori
JS:ssä voimme arvioida merkkijonon js-koodia kahdella tavalla. Ensimmäinen tapa on eval()
-funktion kautta, joka on hieman vaarallinen (se sotkee scope:n), joten sen käyttäminen on erittäin suositeltavaa.
Toinen tapa arvioida merkkijonossa olevaa koodia on Function
-konstruktorin Function
kautta. Tämä konstruktori ottaa argumentteina merkkijonon ja koodin sisältävän merkkijonon. Näin kaikella on oma scope eikä se sotke muiden asioita.
Luo siis periaatteessa uuden funktion näillä muuttujilla (vaihe 5): require
, exports
ja module
. Ajatellaanpa hetki tämän postauksen ensimmäistä esimerkkiä, tiedostoa test.js
: siitä tulee
function(require, exports, module) { module.exports = "Hello World" }
ja toisesta tiedostosta main.js
:
function(require, exports, module) { const test = require("./test.js"); console.log(test) }
Muuttujat, jotka näyttivät tiedostoissa ”globaaleilta”, todellakin välitetään funktion argumentteina.
Viimeinen vaihe: funktion suorittaminen
Olemme luoneet (vaihe 6) muuttujan wrapper
, joka pitää sisällään funktiota, mutta funktiota ei koskaan suoriteta. Teemme tämän rivillä:
wrapper(myRequire, module.exports, module);
Huomaa, että toinen muuttuja (jonka pitäisi olla exports
) on vain kahva muuttujaan module.exports
; NodeJS:n luojat ajattelivat, että tämä olisi voinut auttaa kirjoittamaan vähemmän koodia…
Kun Node suorittaa funktion, kaikki, mitä ”vietiin” (sinun julkinen API:si), linkittyy välimuistiin.
(Muistatko rivin myRequire.cache = module;
? Kun kääntäjä löysi sen ensimmäisen kerran, se osoitti tyhmää { exports: {} }
-objektia; nyt se sisältää moduulisi.)
Huom. Koska välitämme
myRequire
wrapper-funktiolle, voimme tästä lähtien käyttäärequire
testitiedostoissa, mutta require-funktiomme saa kutsun. Lisää console.log, jos et luota minuun 😉
Loppujen lopuksi… myRequire
palauttaa export
ed tavaran, jonka olet ilmoittanut (vaihe 7), ja jonka olemme tallentaneet välimuistiin, jotta meidän ei tarvitse arvioida tätä koodia uudestaan.
Loppuhuomioita
Esimerkki tästä koodista löytyy täältä, sekä joitakin konsolilokeja, jotka selittävät, mitä tapahtuu.
Tämän artikkelin idea on peräisin tämän funktion selityksestä luvussa 10 (Moduulit). Kirja (Eloquent Javascript) on erinomainen, mutta minulla oli halu ymmärtää paremmin ja kokeilla debuggerin avulla sitä, mitä en ymmärtänyt pelkällä järjellä.
Sinun kannattaa ehdottomasti lukea kirja, jos haluat ymmärtää javascriptia paremmin.
Tags: javascript – nodejs