Du bør vide, at Javascript (bedre at sige EcmaScript) ikke specificerer nogen funktion til at læse og skrive filer.

Samtidig er Javascript blot det sprog, der bruges af mange miljøer (browseren eller NodeJS er eksempler på miljøer), der tilbyder flere objekter og funktioner at arbejde med.

Node var det første miljø, der tilbød en måde at organisere kode i moduler på ved hjælp af en særlig funktion kaldet require(). Hvordan fungerer det? Lad os prøve at implementere den fra nul.

Her er et eksempel på require på arbejde:

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

Lad os skrive denne require-funktion.

Hvad skal en require() funktion gøre

En require funktion forventes at:

  • læse indholdet af en javascript-fil i en streng
  • vurdere den kode
  • gemme den eksporterede funktion/det eksporterede objekt i en cache til senere brug (kun læse filer én gang)

Disclaimer

Vi vil ikke genopbygge hele NodeJS i et enkelt indlæg. Faktisk vil jeg ikke implementere mange NodeJS-kontroller og giggles, vi er kun interesseret i at forstå, hvordan tingene fungerer.

Vi vil stadig have brug for den rigtige require-funktion til at indlæse fs-modulet. Jeg snyder ikke, det er bare det, at dette indlæg skal slutte før eller siden 🙂

myRequire() funktion

Her er 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)}...

Glemte du at deklarere myRequire-variablen?

Nej. I Javascript bliver funktioner, der er erklæret med nøgleordet function, evalueret før al anden kode (funktioner er “hoisted”), så de kan refereres, selv før de er erklæret.

Og funktioner kan også have egenskaber (det er javascript!), så du kan tilføje cache-egenskaben til myRequire-funktionen (trin 1).

Endeligt opretter vi cache-egenskaben med Object.create. Med denne funktion kan vi angive objektprototypen, vi har valgt ikke at angive en prototype. Hvorfor? På denne måde roder vi ikke med andre funktioner eller egenskaber, der er deklareret af runtime. Her er en forklaring.

Lad os gå tilbage til myRequire . Hvis den fil, vi importerer, ikke er i cache, læser vi filen fra disken (trin 2).

Derpå erklærer vi et tomt module-objekt med kun én egenskab, exports (trin 3).

Vi tilføjer dette tomme modul til cachen med filnavnet som nøgle, og så sker magien (trin 4).

Funktionskonstruktøren

I JS kan vi evaluere en streng js-kode på to måder. Den første måde er via eval() funktion, der er en smule farlig (den ødelægger scope), så det frarådes stærkt at bruge den.

Den anden måde at evaluere kode, som vi har i en streng, er via Function konstruktøren. Denne konstruktør tager en streng med argumenterne og en streng med koden. På denne måde har alting sit eget scope og ødelægger ikke tingene for andre.

Så i bund og grund opretter vi en ny funktion med disse variabler (trin 5): require, exports og module. Lad os et øjeblik tænke på det første eksempel i dette indlæg, filen test.js: den bliver til

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

og den anden fil, main.js:

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

Variabler, der virkede “globale” i filer, overføres faktisk som funktionsargumenter.

Sidste trin: eksekvering af funktionen

Vi har oprettet (trin 6) en wrapper-variabel, der indeholder en funktion, men funktionen bliver aldrig eksekveret. Det gør vi på linjen:

wrapper(myRequire, module.exports, module); 

Bemærk, at den anden variabel (der skulle være exports) blot er et håndtag til module.exports; NodeJS-skaberne mente, at dette kunne have hjulpet med at skrive mindre kode…

Når Node udfører funktionen, bliver alt, der blev “eksporteret” (dit offentlige API), knyttet til cachen.

(Kan du huske myRequire.cache = module;-linjen? Da den først blev fundet af compileren, pegede den på et dummy { exports: {} }-objekt; nu indeholder den dit modul.)

NOTE. Da vi overdrager myRequire til wrapper-funktionen, kan vi fra nu af bruge require i vores testfiler, men vores require bliver kaldt. Tilføj en console.log, hvis du ikke stoler på mig 😉

Endeligt… myRequire returnerer de exportede ting, som du deklarerede (trin 7), og som vi gemte i cachen, så vi ikke behøver at revurdere denne kode igen.

Slutovervejelser

Et eksempel på denne kode kan findes her, sammen med nogle konsollogs, der forklarer, hvad der foregår.

Ideen til denne artikel stammer fra forklaringen af denne funktion i kapitel 10 (Moduler). Bogen (Eloquent Javascript) er fremragende, men jeg havde lyst til at forstå bedre, og prøve med en debugger, hvad jeg ikke kunne forstå med min hjerne alene.

Du bør helt sikkert læse bogen, hvis du vil forstå javascript bedre.

  • Del på Facebook
  • Tweet
  • Del på LinkedIn

Tags: javascript – nodejs

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.