Du bör veta att Javascript (eller EcmaScript) inte anger någon funktion för att läsa och skriva filer.
I själva verket är Javascript bara ett språk som används av många miljöer (webbläsaren eller NodeJS är exempel på miljöer) som erbjuder fler objekt och funktioner att arbeta med.
Node var den första miljön som erbjöd ett sätt att organisera kod i moduler med hjälp av en särskild funktion som heter require()
. Hur fungerar det? Låt oss försöka implementera den från noll.
Här är ett exempel på require
i arbete:
//test.jsmodule.exports = "Hello World";
//main.jsconst test = require("./test.js"); console.log(test)
Låt oss skriva den där require
-funktionen.
Vad ska en require() funktion göra
En require
funktion förväntas:
- läser innehållet i en javascriptfil i en sträng
- utvärderar den koden
- sparar den exporterade funktionen/objektet i en cache för senare användning (läser bara filer en gång)
Disclaimer
Vi kommer inte att bygga om hela NodeJS i ett enda inlägg. Faktum är att jag inte kommer att implementera många NodeJS-kontroller och fniss, vi är bara intresserade av att förstå hur saker och ting fungerar.
Vi kommer fortfarande att behöva den riktiga require
-funktionen för att ladda fs
-modulen. Jag fuskar inte, det är bara det att det här inlägget måste sluta förr eller senare 🙂
myRequire() function
här är koden:
//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)}...
Glömde du att deklarera variabeln myRequire?
Nej. I Javascript utvärderas funktioner som deklareras med nyckelordet function
före all annan kod (funktioner är ”hoisted”) så att de kan refereras även innan de har deklarerats.
Funktioner kan också ha egenskaper (det här är javascript!) så du kan lägga till egenskapen cache
till myRequire
-funktionen (steg 1).
Slutligt skapar vi cache
-egenskapen med Object.create
. Med den här funktionen kan vi ange objektprototypen, vi har valt att inte ange någon prototyp. Varför? På så sätt ställer vi inte till det för andra funktioner eller egenskaper som deklareras av körtiden. Här är en förklaring.
Låt oss gå tillbaka till myRequire
. Om filen vi importerar inte finns i cacheminnet läser vi filen från disken (steg 2).
Därefter deklarerar vi ett tomt module
-objekt med bara en egenskap, exports
(steg 3).
Vi lägger till denna tomma modul i cachen, med filnamnet som nyckel, och sedan sker magin (steg 4).
Funktionskonstruktören
I JS kan vi utvärdera en sträng js-kod på två sätt. Det första sättet är via eval()
funktion, det är lite farligt (det ställer till det för scope) så det avråds starkt från att använda det.
Det andra sättet att utvärdera kod som vi har i en sträng är via Function
konstruktören. Denna konstruktör tar en sträng med argumenten och en sträng med koden. På så sätt har allting sin egen räckvidd och ställer inte till det för andra.
Så, i princip skapar vi en ny funktion med dessa variabler (steg 5): require
, exports
och module
. Låt oss för ett ögonblick tänka på det första exemplet i det här inlägget, filen test.js
: den blir
function(require, exports, module) { module.exports = "Hello World" }
och den andra filen, main.js
:
function(require, exports, module) { const test = require("./test.js"); console.log(test) }
Variabler som verkade ”globala” i filer skickas faktiskt vidare som funktionsargument.
Sista steget: exekvering av funktionen
Vi har skapat (steg 6) en wrapper
variabel som rymmer en funktion, men funktionen exekveras aldrig. Vi gör detta på raden:
wrapper(myRequire, module.exports, module);
Bemärk att den andra variabeln (som borde vara exports
) bara är ett handtag till module.exports
; NodeJS skapare trodde att detta kunde ha hjälpt till att skriva mindre kod…
När Node exekverar funktionen länkas allt som ”exporterades” (ditt offentliga API) till cacheminnet.
(Kommer du ihåg myRequire.cache = module;
raden? När den först hittades av kompilatorn pekade den på ett dummy { exports: {} }
-objekt; nu innehåller den din modul.)
NOTE. Eftersom vi överlämnar
myRequire
till wrapperfunktionen kan vi från och med nu användarequire
i våra testfiler, men vårt require blir anropat. Lägg till en console.log om du inte litar på mig 😉
Slutligt… myRequire
returnerar export
det du deklarerade (steg 7) och som vi sparade i cachen så att vi inte behöver omvärdera den här koden igen.
Slutöverväganden
Ett exempel på den här koden finns här, tillsammans med några konsolloggar som förklarar vad som händer.
Idén till den här artikeln kommer från förklaringen av den här funktionen i kapitel 10 (Moduler). Boken (Eloquent Javascript) är utmärkt, men jag hade lust att bättre förstå, och prova med en debugger, det som jag inte kunde förstå enbart med mitt sinne.
Du bör definitivt läsa boken om du vill förstå javascript bättre.
Taggar: javascript – nodejs