Penso che il pezzo critico che ti manca sia che il risultato di require("foo")
sia sempre lo stesso oggetto . Considera questo esempio REPL:
> var myHttpModule = require("http")
{ ... }
> myHttpModule.someNewProperty = "someValue"
'someValue'
> require("http").someNewProperty
'someValue'
La seconda chiamata a require("http")
non ha ricreato il modulo http
- ha convocato il modulo solo http
esistente nell'ambiente di esecuzione corrente di Node, che era stato modificato sulla riga precedente.
Quindi, nel tuo esempio, c'è solo un modulo Alpha. Quando Beta e Gamma chiamano require("alpha")
, ottengono ciascuno lo stesso riferimento all'oggetto singleton del modulo Alpha univoco.
(O, per essere perfettamente precisi, stanno ottenendo lo stesso riferimento al valore exports
del modulo Alpha. require
crea a Module
object, ma restituisce solo la proprietà exports
di quel modulo. Se al momento non comprendi completamente la distinzione, non è un grosso problema.)
Dietro le quinte: cosa succede realmente con require
nella cache?
La prima volta che usi require
per includere un modulo, il nodo esegue effettivamente il codice in quel modulo. Il modulo risultante è memorizzato in require.cache
. I successivi tentativi di caricare il modulo con require
verificano innanzitutto un oggetto modulo già caricato in require.cache
. ( Nota: moduli Node integrati nativi dell'ambiente come http
sono eccezioni in quanto non usano require.cache
- dovrai testare il mio codice qui sotto con un modulo personalizzato.)
require.cache
è un oggetto le cui chiavi sono percorso del file del modulo, con valori associati che sono oggetti modulo. Ad esempio, supponiamo che alcuni moduli denominati " foo
" in C:\node_modules\foo.js
:
> require.cache // empty cache
{ }
> require("foo") // require foo
{ bar: 'baz' }
> require.cache // cache now populated
{ 'C:\node_modules\foo.js':
{ id: 'C: \node_modules\foo.js',
exports: { bar: 'baz' },
parent: { ... },
filename: 'C:\node_modules\foo.js',
...
paths:
[ ... ] } }
Il valore corrente del modulo foo
è in require.cache["C:\node_modules\foo.js"].exports
. Possiamo utilizzare require.resolve
per ottenere il percorso del file del modulo dal nome, in modo che possiamo esprimerlo anche come require.cache[require.resolve("foo")].exports
.
Se chiamiamo require("foo")
una seconda volta, Nodo vede che require.cache[require.resolve("foo")]
è definito, e quindi restituisce il valore di require.cache[require.resolve("foo")].exports
invece di rieseguire il codice di creazione del modulo. Questa proprietà exports
del modulo foo
in require.cache
è la singola istanza del valore di esportazione del modulo foo
, restituita con ogni chiamata successiva a require("foo")
.
Un'implicazione interessante qui è che puoi delete require.cache[require.resolve("foo")]
per forzare un ricaricamento del modulo foo
con la prossima chiamata a require("foo")
, perché l'oggetto che descrive il modulo viene rimosso dall'oggetto require.cache
.
Che cosa significa per me?
Senza require
, puoi ancora condividere valori tra moduli usando le variabili globali. Ad esempio, il tuo caso Alpha / Beta / Gamma funzionerebbe altrettanto bene con solo l'impostazione Beta e Gamma e la lettura del valore globale global.a
. Invece, con il sistema require
del nodo, in realtà stai impostando e leggendo global.require.cache[require.resolve("alpha")].exports.a
, il quale nodo ti consente di leggere esattamente come require("alpha").a
.
Infatti, se vuoi solo condividere un valore e non hai bisogno di importare i tuoi dati condivisi come modulo, potresti anche usare un oggetto namespacing, cioè avere beta e gamma per impostare e leggere le proprietà dell'oggetto% codice%. Il vantaggio principale dell'utilizzo di global.alpha
è che non è necessario impostare require
esterno agli altri script, mentre è necessario definire require("alpha")
prima di global.alpha = {}
ing Beta e Gamma. (In alternativa, potresti definire in modo condizionale require
in ogni modulo basato su un controllo global.alpha
, per vedere se è il primo modulo ad usarlo.)