Sie sollten wissen, dass Javascript (besser gesagt EcmaScript) keine Funktion zum Lesen und Schreiben von Dateien vorsieht.

In der Tat ist Javascript nur die Sprache, die von vielen Umgebungen verwendet wird (der Browser oder NodeJS sind Beispiele für Umgebungen), die mehr Objekte und Funktionen zum Arbeiten anbieten.

Node war die erste Umgebung, die eine Möglichkeit bot, Code in Modulen zu organisieren, indem sie eine spezielle Funktion namens require() verwendete. Wie funktioniert das? Versuchen wir, sie von Null an zu implementieren.

Hier ist ein Beispiel für require bei der Arbeit:

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

Lassen Sie uns diese require Funktion schreiben.

Was soll eine require()-Funktion tun

Eine requireFunktion soll das:

  • den Inhalt einer Javascript-Datei in einem String lesen
  • den Code auswerten
  • die exportierte Funktion/das exportierte Objekt in einem Cache zur späteren Verwendung speichern (Dateien nur einmal lesen)

Haftungsausschluss

Wir werden nicht das gesamte NodeJS in einem einzigen Beitrag neu aufbauen. In der Tat werde ich nicht viele NodeJS Checks und Kicherer implementieren, wir sind nur daran interessiert zu verstehen, wie die Dinge funktionieren.

Wir werden immer noch die echte require Funktion benötigen, um das fs Modul zu laden. Ich schummle nicht, es ist nur so, dass dieser Beitrag früher oder später enden muss 🙂

myRequire() Funktion

Hier ist der 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)}...

Hast du vergessen, die myRequire Variable zu deklarieren?

Nein. In Javascript werden Funktionen, die mit dem Schlüsselwort function deklariert werden, vor jedem anderen Code ausgewertet (Funktionen werden „gehievt“), so dass sie referenziert werden können, noch bevor sie deklariert werden.

Außerdem können Funktionen Eigenschaften haben (das ist Javascript!), so dass du die cache-Eigenschaft zur myRequire-Funktion hinzufügen kannst (Schritt 1).

Schließlich erstellen wir die cache-Eigenschaft mit Object.create. Mit dieser Funktion können wir den Objektprototyp angeben, wir haben uns dafür entschieden, keinen Prototyp anzugeben. Warum? Auf diese Weise kommen wir nicht mit anderen Funktionen oder Eigenschaften in Konflikt, die von der Laufzeit deklariert wurden. Hier ist eine Erklärung.

Zurück zu myRequire . Wenn die Datei, die wir importieren, nicht im Cache ist, lesen wir die Datei von der Festplatte (Schritt 2).

Dann deklarieren wir ein leeres module Objekt mit nur einer Eigenschaft, exports (Schritt 3).

Wir fügen dieses leere Modul dem Cache hinzu, wobei wir den Dateinamen als Schlüssel verwenden, und dann passiert die Magie (Schritt 4).

Der Funktionskonstruktor

In JS können wir einen String js-Code auf zwei Arten auswerten. Der erste Weg ist über die eval() Funktion, die ein wenig gefährlich ist (sie bringt den Bereich durcheinander), so dass von ihrer Verwendung dringend abgeraten wird.

Der zweite Weg, den Code in einem String auszuwerten, ist über den Function Konstruktor. Dieser Konstruktor nimmt einen String mit den Argumenten und einen String mit dem Code. Auf diese Weise hat alles seinen eigenen Geltungsbereich und bringt andere nicht durcheinander.

Wir erstellen also im Grunde eine neue Funktion mit diesen Variablen (Schritt 5): require, exports, und module. Denken wir einen Moment an das erste Beispiel dieses Beitrags, die Datei test.js: Sie wird

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

und die zweite Datei, main.js:

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

Variablen, die in Dateien „global“ zu sein schienen, werden tatsächlich als Funktionsargumente übergeben.

Letzter Schritt: Ausführen der Funktion

Wir haben (Schritt 6) eine wrapper-Variable erstellt, die eine Funktion enthält, aber die Funktion wird nie ausgeführt. Wir tun dies in der Zeile:

wrapper(myRequire, module.exports, module); 

Beachten Sie, dass die zweite Variable (die exports sein sollte) nur ein Handle zu module.exports ist; die NodeJS-Schöpfer dachten, dass dies beim Schreiben von weniger Code helfen könnte…

Wenn Node die Funktion ausführt, wird alles, was „exportiert“ wurde (Ihre öffentliche API), mit dem Cache verknüpft.

(Erinnern Sie sich an die Zeile myRequire.cache = module;? Als sie zum ersten Mal vom Compiler gefunden wurde, zeigte sie auf ein Dummy-Objekt { exports: {} }; jetzt enthält sie Ihr Modul.)

Hinweis. Da wir myRequire an die Wrapper-Funktion übergeben, können wir von nun an require in unseren Testdateien verwenden, aber unser require wird aufgerufen. Füge ein console.log hinzu, wenn du mir nicht vertraust 😉

Schließlich… myRequire gibt das exportdeklarierte Zeug zurück, das du deklariert hast (Schritt 7), und das wir im Cache gespeichert haben, damit wir diesen Code nicht noch einmal auswerten müssen.

Abschließende Überlegungen

Ein Beispiel für diesen Code finden Sie hier, zusammen mit einigen Konsolenprotokollen, die erklären, was vor sich geht.

Die Idee zu diesem Artikel stammt aus der Erklärung dieser Funktion in Kapitel 10 (Module). Das Buch (Eloquent Javascript) ist hervorragend, aber ich hatte den Drang, besser zu verstehen und mit einem Debugger auszuprobieren, was ich mit meinem Verstand allein nicht verstehen konnte.

Das Buch sollte man unbedingt lesen, wenn man Javascript besser verstehen will.

  • Teilen auf Facebook
  • Tweet
  • Teilen auf LinkedIn

Tags: javascript – nodejs

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.