Vertieftes Verständnis des Implementierungsprinzips des Require Loader

Vertieftes Verständnis des Implementierungsprinzips des Require Loader

Vorwort

Wir sagen oft, dass Node keine neue Programmiersprache ist, sondern nur eine Laufzeitumgebung für JavaScript. Sie können die Laufzeitumgebung einfach als Umgebung zum Ausführen von JavaScript verstehen. In den meisten Fällen führen wir JavaScript im Browser aus. Mit dem Aufkommen von Node können wir JavaScript in Node ausführen, was bedeutet, dass wir JavaScript überall dort ausführen können, wo Node oder Browser installiert sind.

1. Implementierung der Knotenmodularisierung

Node hat seinen eigenen modularen Mechanismus. Jede Datei ist ein separates Modul und folgt der CommonJS-Spezifikation, d. h. es importiert Module mit require und exportiert Module über module.export.
Der Betriebsmechanismus des Knotenmoduls ist ebenfalls sehr einfach. Tatsächlich wird eine Funktionsschicht außerhalb jedes Moduls eingeschlossen. Durch das Einschließen der Funktion kann die Bereichsisolation zwischen Codes erreicht werden.

Sie könnten sagen, dass ich die Funktion nicht umschlossen habe, als ich den Code geschrieben habe. Ja, das stimmt. Diese Funktionsebene wird von Node automatisch für uns implementiert. Lassen Sie es uns testen.

Wir erstellen eine neue JS-Datei und drucken in der ersten Zeile eine nicht vorhandene Variable. Beispielsweise drucken wir hier „window“, aber im Knoten ist kein „window“ vorhanden.

konsole.log(Fenster);

Wenn Sie die Datei über den Knoten ausführen, wird die folgende Fehlermeldung angezeigt. (Bitte verwenden Sie die systemeigene Standard-Eingabeaufforderung, um den Befehl auszuführen).

(Funktion (Exporte, erfordern, Modul, __Dateiname, __Verzeichnisname) { console.log(Fenster);
ReferenceError: Fenster ist nicht definiert
    bei Objekt.<anonymous> (/Users/choice/Desktop/node/main.js:1:75)
    bei Module._compile (intern/modules/cjs/loader.js:689:30)
    bei Object.Module._extensions..js (intern/modules/cjs/loader.js:700:10)
    bei Module.load (internal/modules/cjs/loader.js:599:32)
    bei tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    bei Function.Module._load (intern/modules/cjs/loader.js:530:3)
    bei Function.Module.runMain (intern/modules/cjs/loader.js:742:12)
    beim Start (internal/bootstrap/node.js:279:19)
    bei bootstrapNodeJSCore (intern/bootstrap/node.js:752:3)

Sie können sehen, dass sich auf der obersten Ebene des Fehlerberichts eine selbstausführende Funktion befindet, die die häufig verwendeten globalen Variablen wie Exporte, Require, Module, __filename und __dirname enthält.

Ich habe dies in meinem vorherigen Artikel „Die Entwicklungsgeschichte der Front-End-Modularität“ vorgestellt. Selbstausführende Funktionen sind auch eine der Lösungen zur Implementierung der Front-End-Modularisierung. In den frühen Tagen, als es im Front-End noch kein modulares System gab, konnten selbstausführende Funktionen das Namespace-Problem sehr gut lösen, und andere Module, von denen das Modul abhängt, konnten über Parameter übergeben werden. Auch die cmd- und amd-Spezifikationen basieren auf selbstausführenden Funktionen.

Im Modulsystem ist jede Datei ein Modul. Jedes Modul hat automatisch eine Funktion außerhalb und definiert die Exportmethode module.exports oder exports sowie die Importmethode require.

let moduleA = (funktion() {
    module.exports = Versprechen;
    Rückgabemodul.Exporte;
})();

2.Lademodul erforderlich

require verlässt sich auf das fs-Modul im Knoten, um Moduldateien zu laden, und fs.readFile liest eine Zeichenfolge.

In JavaScript können wir die Funktion „eval“ oder „new“ verwenden, um einen String in ausführbaren JS-Code umzuwandeln.

Auswertung

konstanter Name = "yd";
const str = 'const a = 123; console.log(Name)';
eval(str); // yd;

neue Funktion

Die neue Funktion empfängt einen auszuführenden String und gibt eine neue Funktion zurück. Durch Aufrufen dieses neuen Funktionsstrings wird dieser ausgeführt. Wenn diese Funktion Parameter übergeben muss, können Sie die Parameter beim Erstellen der neuen Funktion einzeln übergeben und schließlich die auszuführende Zeichenfolge übergeben. Beispielsweise übergeben wir hier im Parameter b den auszuführenden String str.

Konstante b = 3;
const str = 'lass a = 1; returniere a + b';
const fun = neue Funktion('b', str);
konsole.log(spaß(b, str)); // 4

Sie können sehen, dass sowohl die Auswertung als auch die Funktionsinstanziierung zum Ausführen von JavaScript-Zeichenfolgen verwendet werden können und dass beide das Laden erforderlicher Module implementieren können. Sie wurden jedoch nicht ausgewählt, um Modularität in Node zu implementieren. Der Grund ist ganz einfach: Sie alle haben ein schwerwiegendes Problem, nämlich dass sie leicht von Variablen beeinflusst werden, die ihnen nicht gehören.

Wie unten gezeigt, ist a in der Zeichenfolge str nicht definiert, aber die oben definierte Variable a kann verwendet werden. Das ist offensichtlich falsch. Im modularen Mechanismus sollte die Zeichenfolge str ihren eigenen unabhängigen Ausführungsbereich haben, und Variablen, die nicht in sich selbst existieren, können nicht direkt verwendet werden.

Konstante a = 1;

const str = "console.log(a)";

eval(str);

const func = neue Funktion (str);
Funktion();

Node verfügt über ein Konzept einer virtuellen VM-Umgebung, die zum Ausführen zusätzlicher JS-Dateien verwendet wird. Es kann die Unabhängigkeit der JavaScript-Ausführung sicherstellen und wird nicht von externen Faktoren beeinflusst.

integriertes VM-Modul

Obwohl wir „Hallo“ extern definiert haben, ist str ein unabhängiges Modul und befindet sich nicht in der Dorf-Hallo-Variable, sodass ein Fehler direkt gemeldet wird.

// VM-Modul importieren, keine Installation nötig, vom Knoten selbst erstelltes Modul const vm = require('vm');
const hallo = "yd";
const str = 'console.log(hallo)';
wm.runInThisContext(str); // Fehler

Daher kann der Knoten VM verwenden, um JavaScript-Module auszuführen. Dadurch wird die Unabhängigkeit der Module gewährleistet.

3. Code-Implementierung erforderlich

Bevor wir die erforderliche Codeimplementierung vorstellen, überprüfen wir die Verwendung von zwei Knotenmodulen, da diese im Folgenden verwendet werden.

Das Pfadmodul

Wird zum Verarbeiten von Dateipfaden verwendet.

Basisname: Basispfad. Wenn ein Dateipfad vorhanden ist, ist dies nicht der Basispfad. Der Basispfad ist 1.js.

extname: Ruft den Erweiterungsnamen ab

dirname: übergeordnetes Verzeichnis

Join: Pfade verketten

resolve: Der absolute Pfad des aktuellen Ordners. Achten Sie darauf, bei der Verwendung kein / am Ende hinzuzufügen.

__dirname: Der Pfad des Ordners, in dem sich die aktuelle Datei befindet

__filename: Der absolute Pfad der aktuellen Datei

const Pfad = erforderlich('Pfad', 's');
console.log(Pfad.Basisname('1.js'));
console.log(Pfad.Erweiterungsname('2.txt'));
console.log(Pfad.Verzeichnisname('2.txt'));
console.log(Pfad.join('a/b/c', 'd/e/f')); // a/b/c/d/e/
console.log(Pfad.auflösen('2.txt'));

fs-Modul

Wird zum Bedienen von Dateien oder Ordnern verwendet, z. B. zum Lesen, Schreiben, Hinzufügen, Löschen usw. Häufig verwendete Methoden sind readFile und readFileSync, die jeweils asynchrones und synchrones Lesen von Dateien ermöglichen.

const fs = erfordern('fs');
const buffer = fs.readFileSync('./name.txt', 'utf8'); // Wenn keine Kodierung übergeben wird, wird die Binärdatei ausgegeben console.log(buffer);

fs.access: Bestimmen Sie, ob eine Datei vorhanden ist. Die von node10 bereitgestellte Methode exists ist veraltet, da sie nicht der Knotenspezifikation entspricht. Daher verwenden wir access, um zu bestimmen, ob eine Datei vorhanden ist.

versuchen {
    fs.accessSync('./name.txt');
} Fang(e) {
    // Datei existiert nicht}

4. Manuelles Implementieren des erforderlichen Modulladers

Importieren Sie zunächst den abhängigen Modulpfad, fs, vm und erstellen Sie eine Require-Funktion, die einen modulePath-Parameter empfängt, der den zu importierenden Dateipfad angibt.

// Abhängigkeit importieren const path = require('path'); // Pfadoperation const fs = require('fs'); // Datei lesen const vm = require('vm'); // Datei ausführen // Importklasse definieren, Parameter ist Modulpfad function Require(modulePath) {
    ...
}

Holen Sie sich den absoluten Pfad des Moduls in Require, was praktisch ist, um das Modul mit fs zu laden. Hier verwenden wir new Module, um das Lesen des Modulinhalts zu abstrahieren, und verwenden tryModuleLoad, um den Modulinhalt zu laden. Wir werden Module und tryModuleLoad später implementieren. Der Rückgabewert von Require sollte der Inhalt des Moduls sein, also module.exports.

//Definiere die Importklasse, der Parameter ist der Modulpfad function Require(modulePath) {
    // Den absoluten Pfad zum Laden abrufen let absPathname = path.resolve(__dirname, modulePath);
    // Ein Modul erstellen und eine neue Modulinstanz erstellen const module = new Module(absPathname);
    // Aktuelles Modul laden tryModuleLoad(module);
    // Exportobjekt zurückgeben return module.exports;
}

Die Implementierung von Module ist sehr einfach. Es wird ein Exportobjekt für das Modul erstellt. Wenn tryModuleLoad ausgeführt wird, wird der Inhalt zu den Exporten hinzugefügt. Die ID ist der absolute Pfad des Moduls.

// Modul definieren, Datei-ID hinzufügen und Attributfunktion exportieren Module(id) {
    diese.id = ID;
    // Der gelesene Dateiinhalt wird in Exporte eingefügt this.exports = {};
}

Wir haben bereits erwähnt, dass das Knotenmodul in einer Funktion ausgeführt wird. Hier mounten wir den statischen Eigenschaftenwrapper in das Modul, der die Zeichenfolge dieser Funktion definiert. Der Wrapper ist ein Array, und das erste Element des Arrays ist der Parameterteil der Funktion, einschließlich Exporte, Modul. Require, __dirname, __filename, die alle globale Variablen sind, die häufig in unseren Modulen verwendet werden. Beachten Sie, dass der hier übergebene Require-Parameter der von uns selbst definierte Require ist.

Der zweite Parameter ist das Ende der Funktion. Beide Teile sind Zeichenfolgen. Wenn wir sie verwenden, packen wir sie einfach außerhalb der Modulzeichenfolge ein.

Modul.wrapper = [
    "(Funktion(Export, Modul, Erfordert, __Verzeichnisname, __Dateiname) {",
    "})"
]

_extensions wird verwendet, um unterschiedliche Lademethoden für unterschiedliche Modulerweiterungen zu verwenden. Beispielsweise sind die Lademethoden von JSON und Javascript definitiv unterschiedlich. JSON wird mit JSON.parse analysiert.

JavaScript wird mit vm.runInThisContext ausgeführt. Wir können sehen, dass fs.readFileSync module.id übergibt, was bedeutet, dass die in unserer Moduldefinition gespeicherte ID der absolute Pfad des Moduls ist. Der gelesene Inhalt ist eine Zeichenfolge. Wir verwenden Module.wrapper, um ihn zu umschließen, was dem Umschließen einer Funktion außerhalb dieses Moduls entspricht und so einen privaten Bereich realisiert.

Verwenden Sie call, um die fn-Funktion auszuführen. Der erste Parameter ändert das Ausführen von this. Wir übergeben module.exports. Die folgenden Parameter sind die Parameter, die außerhalb der Funktion eingeschlossen sind: exports, module, Require, __dirname, __filename

Module._Erweiterungen = {
    '.js'(Modul) {
        const Inhalt = fs.readFileSync(module.id, 'utf8');
        const fnStr = Modul.wrapper[0] + Inhalt + Modul.wrapper[1];
        const fn = vm.runInThisContext(fnStr);
        fn.call(module.exports, module.exports, module, Erfordert,_Dateiname,_Verzeichnisname);
    },
    '.json'(Modul) {
        const json = fs.readFileSync(module.id, 'utf8');
        module.exports = JSON.parse(json); // Setze das Ergebnis der Datei in die Exporteigenschaft ein}
}

Die Funktion tryModuleLoad empfängt das Modulobjekt, erhält das Modulsuffix über path.extname und verwendet dann Module._extensions, um das Modul zu laden.

//Methode zum Laden des Moduls definieren function tryModuleLoad(module) {
    // Den Erweiterungsnamen abrufen const extension = path.extname(module.id);
    //Lade das aktuelle Modul nach Suffix Module._extensions[extension](module);
}

An diesem Punkt haben wir den Require-Lademechanismus im Wesentlichen fertig geschrieben. Schauen wir ihn uns noch einmal an. Wenn Require ein Modul lädt, übergeben Sie den Modulnamen und verwenden Sie path.resolve(__dirname, modulePath) in der Require-Methode, um den absoluten Pfad der Datei zu erhalten. Erstellen Sie dann ein Modulobjekt durch neue Modulinstanzierung, speichern Sie den absoluten Pfad des Moduls im ID-Attribut des Moduls und erstellen Sie das Exportattribut im Modul als JSON-Objekt.

Verwenden Sie die Methode tryModuleLoad, um das Modul zu laden. Verwenden Sie in tryModuleLoad path.extname, um die Dateierweiterung abzurufen, und führen Sie dann basierend auf der Erweiterung den entsprechenden Modullademechanismus aus.

Das geladene Modul wird schließlich in module.exports gemountet. Nachdem tryModuleLoad ausgeführt wurde, ist module.exports bereits vorhanden. Kehren Sie also einfach direkt zurück.

// Abhängigkeit importieren const path = require('path'); // Pfadoperation const fs = require('fs'); // Datei lesen const vm = require('vm'); // Datei ausführen // Importklasse definieren, Parameter ist Modulpfad function Require(modulePath) {
    // Den absoluten Pfad zum Laden abrufen let absPathname = path.resolve(__dirname, modulePath);
    // Ein Modul erstellen und eine neue Modulinstanz erstellen const module = new Module(absPathname);
    // Aktuelles Modul laden tryModuleLoad(module);
    // Exportobjekt zurückgeben return module.exports;
}
// Modul definieren, Datei-ID hinzufügen und Attributfunktion exportieren Module(id) {
    diese.id = ID;
    // Der gelesene Dateiinhalt wird in Exporte eingefügt this.exports = {};
}
// Definieren Sie die Funktion, die den Modulinhalt umschließt Module.wrapper = [
    "(Funktion(Export, Modul, Erfordert, __Verzeichnisname, __Dateiname) {",
    "})"
]
// Definieren Sie den Erweiterungsnamen. Unterschiedliche Erweiterungsnamen haben unterschiedliche Lademethoden. Implementieren Sie js und json
Module._Erweiterungen = {
    '.js'(Modul) {
        const Inhalt = fs.readFileSync(module.id, 'utf8');
        const fnStr = Modul.wrapper[0] + Inhalt + Modul.wrapper[1];
        const fn = vm.runInThisContext(fnStr);
        fn.call(module.exports, module.exports, module, Erfordert,_Dateiname,_Verzeichnisname);
    },
    '.json'(Modul) {
        const json = fs.readFileSync(module.id, 'utf8');
        module.exports = JSON.parse(json); // Setze das Ergebnis der Datei in die Exporteigenschaft ein}
}
//Methode zum Laden des Moduls definieren function tryModuleLoad(module) {
    // Den Erweiterungsnamen abrufen const extension = path.extname(module.id);
    //Lade das aktuelle Modul nach Suffix Module._extensions[extension](module);
}

5. Cache zum Modul hinzufügen

Auch das Hinzufügen eines Caches ist relativ einfach. Wenn Sie eine Datei laden, legen Sie die Datei in den Cache. Überprüfen Sie beim Laden eines Moduls, ob es im Cache vorhanden ist. Wenn es vorhanden ist, verwenden Sie es direkt. Wenn es nicht vorhanden ist, laden Sie es erneut und legen Sie es nach dem Laden in den Cache.

//Definiere die Importklasse, der Parameter ist der Modulpfad function Require(modulePath) {
    // Den absoluten Pfad zum Laden abrufen let absPathname = path.resolve(__dirname, modulePath);
    // Aus dem Cache lesen, falls vorhanden, Ergebnis direkt zurückgeben if (Module._cache[absPathname]) {
        gibt Module._cache[absPathname].exports zurück;
    }
    // Versuchen Sie, das aktuelle Modul zu laden tryModuleLoad(module);
    // Ein Modul erstellen und eine neue Modulinstanz erstellen const module = new Module(absPathname);
    // Cache hinzufügen Module._cache[absPathname] = module;
    // Aktuelles Modul laden tryModuleLoad(module);
    // Exportobjekt zurückgeben return module.exports;
}

6. Pfad automatisch vervollständigen

Fügen Sie dem Modul automatisch ein Suffix hinzu, um das Modul ohne Suffix zu laden. Wenn die Datei kein Suffix hat, werden tatsächlich alle Suffixe durchsucht, um festzustellen, ob die Datei vorhanden ist.

//Definiere die Importklasse, der Parameter ist der Modulpfad function Require(modulePath) {
    // Den absoluten Pfad zum Laden abrufen let absPathname = path.resolve(__dirname, modulePath);
    // Alle Suffixnamen abrufen const extNames = Object.keys(Module._extensions);
    lass Index = 0;
    //Den ursprünglichen Dateipfad speichern const oldPath = absPathname;
    Funktion findExt(absPathname) {
        wenn (index === extNames.Länge) {
           return throw new Error('Datei existiert nicht');
        }
        versuchen {
            fs.accessSync(absPathname);
            gibt absPathname zurück;
        } Fang(e) {
            const ext = extNames[index++];
            findExt(alterPfad + ext);
        }
    }
    // Den Suffixnamen rekursiv anhängen, um zu ermitteln, ob die Datei existiert absPathname = findExt(absPathname);
    // Aus dem Cache lesen, falls vorhanden, Ergebnis direkt zurückgeben if (Module._cache[absPathname]) {
        gibt Module._cache[absPathname].exports zurück;
    }
    // Versuchen Sie, das aktuelle Modul zu laden tryModuleLoad(module);
    // Ein Modul erstellen und eine neue Modulinstanz erstellen const module = new Module(absPathname);
    // Cache hinzufügen Module._cache[absPathname] = module;
    // Aktuelles Modul laden tryModuleLoad(module);
    // Exportobjekt zurückgeben return module.exports;
}

7. Analyse- und Umsetzungsschritte

1. Importieren Sie zugehörige Module und erstellen Sie eine Require-Methode.

2. Extrahieren Sie über die Methode Module._load, die zum Laden des Moduls verwendet wird.

3.Module.resolveFilename wandelt den relativen Pfad in einen absoluten Pfad um.

4. Cache-Modul Module._cache, laden Sie nicht dasselbe Modul wiederholt, um die Leistung zu verbessern.

5. Modul-ID erstellen: Der gespeicherte Inhalt ist „Exports = {}“, was dem hier entspricht.

6. Verwenden Sie tryModuleLoad(Modul, Dateiname), um zu versuchen, das Modul zu laden.

7.Module._extensions verwendet Lesedateien.

8.Module.wrap: Umschließen Sie das gelesene JS mit einer Funktion.

9. Führen Sie die erhaltene Zeichenfolge mit runInThisContext aus.

10. Lassen Sie den String ausführen und passen Sie diesen an die Exporte an.

Zusammenfassen

Dies ist das Ende dieses Artikels über das Implementierungsprinzip des Require Loader. Weitere Informationen zum Prinzip des Require Loader finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

Das könnte Sie auch interessieren:
  • Tiefgreifendes Verständnis von requireJS - Implementierung eines einfachen Modulladers

<<:  So entfernen Sie Leerzeichen oder bestimmte Zeichen in einer Zeichenfolge in der Shell

>>:  So beheben Sie Dateisystemfehler in Linux mit „fsck“

Artikel empfehlen

Eine kurze Erläuterung zur Verwendung von Slots in Vue

Definition und Verwendung: Verwenden Sie die Slot...

Mit wie vielen Pixeln sollte eine Webseite gestaltet werden?

Viele Webdesigner sind beim Entwurf des Webseitenl...

Lösen Sie das Problem, dass ifconfig im Docker nicht verfügbar ist

Als ich kürzlich Docker lernte, stellte ich fest,...

Grundlegende MySQL-Tabellenabfragen – häufige Fehler beim Left-Join

Überblick Bei kleinen und mittelgroßen Projekten ...

Detaillierte Einführung in Robots.txt

Grundlegende Einführung in robots.txt Robots.txt i...

So zeichnen Sie in CocosCreator ein cooles Radardiagramm

Inhaltsverzeichnis Vorwort Vorschau Text Grafikko...

Zusammenfassung der Anwendung von Übergangskomponenten in Vue-Projekten

​Transtion in Vue ist eine Kapselungskomponente f...

XHTML drei Dokumenttypdeklarationen

XHTML definiert drei Dokumenttypdeklarationen. Am...

So installieren Sie Nginx und konfigurieren mehrere Domänennamen

Nginx-Installation CentOS 6.x yum verfügt standar...