Je moet weten dat Javascript (beter te zeggen EcmaScript) geen functie specificeert om bestanden te lezen en te schrijven.

In feite is Javascript slechts de taal die wordt gebruikt door vele omgevingen (de browser, of NodeJS, zijn voorbeelden van omgevingen) die meer objecten en functies bieden om mee te werken.

Node was de eerste omgeving die een manier bood om code te organiseren in modules door gebruik te maken van een speciale functie genaamd require(). Hoe werkt het? Laten we proberen om het vanaf nul te implementeren.

Hier is een voorbeeld van require aan het werk:

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

Laten we die require functie schrijven.

Wat moet een require() functie doen

van een require functie wordt verwacht dat:

  • de inhoud van een javascript-bestand in een string lezen
  • die code evalueren
  • de geëxporteerde functie/object in een cache opslaan voor later gebruik (bestanden slechts eenmaal lezen)

Disclaimer

We zullen niet de hele NodeJS in één post opnieuw opbouwen. In feite zal ik niet implementeren veel NodeJS controles en giecheltjes, we zijn alleen geïnteresseerd in te begrijpen hoe dingen werken.

We zullen nog steeds de echte require functie nodig om de fs module te laden. Ik speel niet vals, het is gewoon dat deze post vroeg of laat moet eindigen 🙂

myRequire() functie

hier is de code:

//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)}...

Ben je vergeten de variabele myRequire te declareren?

Nee. In Javascript worden functies die met function keyword worden gedeclareerd, geëvalueerd voordat andere code wordt gebruikt (functies worden “getakeld”), zodat ernaar kan worden verwezen, zelfs voordat ze zijn gedeclareerd.

Ook kunnen functies eigenschappen hebben (dit is javascript!), dus je kunt de cache eigenschap toevoegen aan de myRequire functie (stap 1).

Eindelijk maken we de cache eigenschap met Object.create. Met deze functie kunnen we het object prototype opgeven, wij hebben ervoor gekozen om geen prototype op te geven. Waarom? Op deze manier knoeien we niet met andere functies of eigenschappen die door de runtime zijn gedeclareerd. Hier volgt een uitleg.

Laten we teruggaan naar myRequire . Als het bestand dat we importeren niet in de cache staat, lezen we het bestand van schijf (stap 2).

Dan declareren we een leeg module object met slechts één eigenschap, exports (stap 3).

We voegen deze lege module toe aan de cache, met de bestandsnaam als sleutel, en dan gebeurt de magie (stap 4).

De Functieconstructor

In JS kunnen we een string js code op twee manieren evalueren. De eerste manier is via eval() functie, dat is een beetje gevaarlijk (het verknoeit de scope) dus het wordt ten zeerste afgeraden om het te gebruiken.

De tweede manier om code die we in een string hebben te evalueren is via de Function constructor. Deze constructor neemt een string met de argumenten en een string met de code. Op deze manier heeft alles zijn eigen bereik en verknoeit het de dingen niet voor anderen.

Dus, in feite maken we een nieuwe functie met deze variabelen (stap 5): require, exports, en module. Laten we even denken aan het eerste voorbeeld van deze post, het bestand test.js: het wordt

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

en het tweede bestand, main.js:

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

Variabelen die “globaal” leken in bestanden worden inderdaad doorgegeven als functie-argumenten.

Laatste stap: uitvoeren van de functie

We hebben (stap 6) een wrapper variabele aangemaakt die een functie bevat, maar de functie wordt nooit uitgevoerd. We doen dit op de regel:

wrapper(myRequire, module.exports, module); 

Noteer dat de tweede variabele (die exports zou moeten zijn) gewoon een handle naar module.exports is; de makers van NodeJS dachten dat dit had kunnen helpen bij het schrijven van minder code…

Wanneer Node de functie uitvoert, wordt alles wat “geëxporteerd” was (je publieke API) gekoppeld aan de cache.

(Herinner je je de myRequire.cache = module; regel? Toen het voor het eerst door de compiler werd gevonden, wees het naar een dummy { exports: {} }-object; nu bevat het uw module.)

-NOOT. Omdat we myRequire doorgeven aan de wrapper functie, kunnen we vanaf nu require gebruiken in onze test bestanden, maar onze require wordt aangeroepen. Voeg een console.log toe als je me niet vertrouwt 😉

Tot slot… myRequire retourneert de exported dingen die je hebt gedeclareerd (stap 7), en die we in de cache hebben opgeslagen zodat we deze code niet opnieuw hoeven te evalueren.

Eindoverwegingen

Een voorbeeld van deze code is hier te vinden, samen met wat console logs die uitleggen wat er aan de hand is.

Het idee van dit artikel komt van de uitleg van deze functie in hoofdstuk 10 (Modules). Het boek (Eloquent Javascript) is uitstekend, maar ik had de drang om beter te begrijpen, en te proberen met een debugger, wat ik niet kon begrijpen met mijn verstand alleen.

Je moet zeker het boek lezen als je javascript beter wilt begrijpen.

  • Deel op Facebook
  • Tweet
  • Deel op LinkedIn

Tags: javascript – nodejs

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.