Měli byste vědět, že Javascript (lépe řečeno EcmaScript) neurčuje žádnou funkci pro čtení a zápis souborů.
V podstatě je Javascript jen jazyk, který používá mnoho prostředí (příkladem prostředí je prohlížeč nebo NodeJS), která nabízejí více objektů a funkcí pro práci.
Node byl prvním prostředím, které nabízelo možnost uspořádat kód do modulů pomocí speciální funkce require()
. Jak to funguje? Zkusme ji implementovat od nuly.
Tady je příklad funkce require
při práci:
//test.jsmodule.exports = "Hello World";
//main.jsconst test = require("./test.js"); console.log(test)
Napíšeme tuto funkci require
.
Co by měla dělat funkce require()
od funkce require
se očekává:
- přečíst obsah javascriptového souboru v řetězci
- vyhodnotit tento kód
- uložit exportovanou funkci/objekt do mezipaměti pro pozdější použití (soubory číst jen jednou)
Odmítnutí odpovědnosti
V jednom příspěvku nebudeme předělávat celý NodeJS. Ve skutečnosti nebudu implementovat mnoho kontrol a chichotání NodeJS, jde nám jen o to, abychom pochopili, jak věci fungují.
Stále budeme potřebovat skutečnou funkci require
pro načtení modulu fs
. Nepodvádím, jen tento příspěvek musí dříve nebo později skončit 🙂
funkce myRequire()
tady je kód:
//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)}...
Zapomněl jsi deklarovat proměnnou myRequire?
Ne. V Javascriptu se funkce deklarované klíčovým slovem function
vyhodnocují před ostatním kódem (funkce se „zvedají“), takže se na ně lze odkazovat ještě před jejich deklarací.
Funkce také mohou mít vlastnosti (to je Javascript!), takže do funkce myRequire
(krok 1) můžete přidat vlastnost cache
.
Nakonec vytvoříme vlastnost cache
pomocí Object.create
. Pomocí této funkce můžeme zadat prototyp objektu, my jsme se rozhodli prototyp nezadávat. Proč? Nebudeme si tak zahrávat s jinými funkcemi nebo vlastnostmi deklarovanými runtime. Zde je vysvětlení:
Vraťme se zpět k myRequire
. Pokud soubor, který importujeme, není v mezipaměti, načteme jej z disku (krok 2).
Poté deklarujeme prázdný objekt module
s jedinou vlastností exports
(krok 3).
Tento prázdný modul přidáme do mezipaměti, jako klíč použijeme název souboru, a pak se stane kouzlo (krok 4).
Konstruktor funkce
V JS můžeme vyhodnotit řetězec js kódu dvěma způsoby. První způsob je přes funkci eval()
, která je trochu nebezpečná (rozhází rozsah), takže se velmi nedoporučuje ji používat.
Druhý způsob vyhodnocení kódu, který máme v řetězci, je přes konstruktor Function
. Tento konstruktor přijímá řetězec s argumenty a řetězec s kódem. Tímto způsobem má vše svůj vlastní obor a nekazí to ostatním.
Takže v podstatě vytváříme novou funkci s těmito proměnnými (krok 5): require
, exports
a module
. Zamysleme se na chvíli nad prvním příkladem tohoto příspěvku, souborem test.js
: stane se z něj
function(require, exports, module) { module.exports = "Hello World" }
a druhý soubor, main.js
:
function(require, exports, module) { const test = require("./test.js"); console.log(test) }
Proměnné, které se v souborech zdály „globální“, jsou skutečně předávány jako argumenty funkce.
Poslední krok: spuštění funkce
Vytvořili jsme (krok 6) proměnnou wrapper
, která obsahuje funkci, ale funkce se nikdy nespustí. Provedeme to na řádku:
wrapper(myRequire, module.exports, module);
Všimněte si, že druhá proměnná (která by měla být exports
) je jen handle na module.exports
; tvůrci NodeJS si mysleli, že by to mohlo pomoci při psaní menšího množství kódu…
Když Node provede funkci, vše, co bylo „exportováno“ (vaše veřejné API), se propojí s cache.
(Pamatujete si na řádek myRequire.cache = module;
? Když ho překladač našel poprvé, ukazoval na fiktivní objekt { exports: {} }
; nyní obsahuje váš modul.“
POZN. Protože funkci wrapper předáváme
myRequire
, můžeme od této chvíle v našich testovacích souborech používatrequire
, ale naše require se zavolá. Pokud mi nevěříte, přidejte console.log 😉
Nakonec… myRequire
vrátí export
deklarované věci (krok 7), které jsme uložili do mezipaměti, abychom tento kód nemuseli znovu vyhodnocovat.
Závěrečné úvahy
Příklad tohoto kódu najdete zde spolu s několika konzolovými logy, které vysvětlují, co se děje.
Námět tohoto článku vychází z vysvětlení této funkce v kapitole 10 (Moduly). Kniha (Eloquent Javascript) je výborná, ale měl jsem nutkání lépe pochopit a vyzkoušet si pomocí debuggeru to, co jsem nedokázal pochopit pouhým rozumem.
Knihu byste si určitě měli přečíst, pokud chcete lépe pochopit javascript.
Značky: javascript – nodejs