Come si condividono le costanti nei moduli NodeJS?


240

Attualmente sto facendo questo:

foo.js

const FOO = 5;

module.exports = {
    FOO: FOO
};

E usandolo in bar.js:

var foo = require('foo');
foo.FOO; // 5

C'è un modo migliore per farlo? È strano dichiarare la costante nell'oggetto export.


6
Se vuoi esportarlo, lo metti in exports. Cosa c'è di strano in questo?
Alex Wayne,

5
Sono abituato a C # e PHP. Immagino che devo solo abituarmi a definire ogni costante due volte. Forse in futuro avremo export const FOO = 5;.
Torre del

1
@Tower Il futuro è adesso (ES2015)! 2ality.com/2014/09/…
Spagna Train

1
Funzionalmente è diverso dal più conciso module.exports={FOO:5};?
Joe Lapp,

3
Non si sente solo insopportabile, non è più costante
Ini

Risposte:


97

È possibile esportarlo esplicitamente nell'ambito globale con global.FOO = 5. Quindi devi semplicemente richiedere il file e nemmeno salvare il valore restituito.

Ma davvero, non dovresti farlo. Mantenere le cose correttamente incapsulate è una buona cosa. Hai già l'idea giusta, quindi continua a fare quello che stai facendo.


51
Mi dispiace farlo, ma -1 per conoscere meglio ma non fornire una soluzione (migliore) alternativa; (ri: "Ma davvero, non dovresti farlo. Mantenere le cose correttamente incapsulate è una buona cosa.")
Grazie

22
Se l'intera comunità di sviluppo software pensasse in questo modo, continueremmo ad usare i punchard. Fortunatamente ci sono alcuni maverick là fuori che sanno quando è meglio infrangere le regole folli che ci imponiamo. Se l'incapsulamento è utile, usalo. Se è una tata nervosa che ti impedisce di fare il tuo lavoro, licenzia la tata nervosa e vai avanti.
non sincronizzato il

22
@naomik (tempo di risposta super late) Il vero motivo per cui non ho fornito una soluzione migliore è perché l'OP conosce già la soluzione. Incapsulare le cose nel loro modulo e richiederle dove necessario.
Alex Wayne,

1
Quindi questa non è una risposta reale e dovrebbe piuttosto essere un commento esplicativo che affermi "stai facendo abbastanza bene, le alternative sono cattive" ..
Andrey Popov

1
Applicazione errata dell'incapsulamento. Quando una classe utilizza valori speciali come indicatori e dà loro un nome, VUOI condividerlo con qualsiasi codice che utilizza quella classe.
grantwparks,

314

A mio avviso, l'utilizzo Object.freezeconsente un DRYer e uno stile più dichiarativo. Il mio modello preferito è:

./lib/constants.js

module.exports = Object.freeze({
    MY_CONSTANT: 'some value',
    ANOTHER_CONSTANT: 'another value'
});

./lib/some-module.js

var constants = require('./constants');

console.log(constants.MY_CONSTANT); // 'some value'

constants.MY_CONSTANT = 'some other value';

console.log(constants.MY_CONSTANT); // 'some value'

Avviso sulle prestazioni obsolete

Il seguente problema è stato risolto in v8 a gennaio 2014 e non è più rilevante per la maggior parte degli sviluppatori:

Essere consapevoli del fatto che sia l'impostazione scrivibile su falso che l'utilizzo di Object.freeze hanno una penosa riduzione delle prestazioni in v8 - https://bugs.chromium.org/p/v8/issues/detail?id=1858 e http://jsperf.com / prestazioni-frozen-oggetto


4
Buon caso d'uso per Object.freeze!
Estus Flask,

Come dovrebbe essere se devo esportare sia costanti che funzioni? Devo inserire anche le funzioni nel blocco di congelamento?
Tom,

3
questo approccio è migliore perché il completamento automatico IDE funziona con esso.
David A

3
Questa è un'ottima risposta, ma potrebbe allontanare le persone da questo approccio a causa dell'avvertimento obsoleto sulle prestazioni di v8 alla fine. Si prega di considerare di rimuovere l'avviso.
sampathsris,

4
Grazie @Krumia! L'ho aggiornato, ma ho lasciato il testo di avvertimento originale solo per il contesto storico (e poiché alcuni di questi commenti non avrebbero senso senza di esso).
Spagna Train

163

Tecnicamente, constnon fa parte delle specifiche ECMAScript. Inoltre, usando il modello "Modulo CommonJS" che hai notato, puoi modificare il valore di quella "costante" poiché ora è solo una proprietà dell'oggetto. (non sono sicuro se ciò porterà in cascata eventuali modifiche ad altri script che richiedono lo stesso modulo, ma è possibile)

Per ottenere una vera e propria costante che si può anche condividere, controllare Object.create, Object.definePropertye Object.defineProperties. Se si imposta writable: false, quindi il valore nella "costante" non può essere modificato. :)

È un po 'prolisso (ma anche quello può essere cambiato con un po' di JS) ma dovresti farlo solo una volta per il tuo modulo di costanti. Usando questi metodi, qualsiasi attributo che si lascia fuori per impostazione predefinita false. (al contrario di definire le proprietà tramite assegnazione, che per impostazione predefinita imposta tutti gli attributi true)

Quindi, ipoteticamente, potresti semplicemente impostare valuee enumerable, tralasciando writablee configurabledato che per impostazione predefinita false, li ho appena inclusi per chiarezza.

Aggiornamento - Ho creato un nuovo modulo ( costanti di nodo ) con funzioni di supporto proprio per questo caso d'uso.

constants.js - Bene

Object.defineProperty(exports, "PI", {
    value:        3.14,
    enumerable:   true,
    writable:     false,
    configurable: false
});

constants.js: meglio

function define(name, value) {
    Object.defineProperty(exports, name, {
        value:      value,
        enumerable: true
    });
}

define("PI", 3.14);

script.js

var constants = require("./constants");

console.log(constants.PI); // 3.14
constants.PI = 5;
console.log(constants.PI); // still 3.14

2
@AntoineHedgecock Non è necessario, consultare la documentazione Object.defineProperty(). Tutte le proprietà non specificate sono assunte falsein questo contesto.
Dominic Barnes

6
Notevole anche Object.freeze ()
damianb

1
Questa è la migliore risposta a questa domanda. +1. Se potessi lo voterei di più.
Ryan,

1
Risposta meravigliosa, una soluzione molto elegante e sicura.
Alex,

1
@SpainTrain Questo sembra essere stato corretto da codereview.chromium.org/135903014
Grinde

100

Modo ES6.

esportare in foo.js

const FOO = 'bar';
module.exports = {
  FOO
}

import in bar.js

const {FOO} = require('foo');

40
Sì. Stack Overflow ha bisogno di un modo per deprezzare le risposte obsolete.
Rick Jolly,

7
Si noti che è constin bar.jsche impone l'immutabilità della variabile destrutturata, non constin foo.js. Cioè, si può usare let {FOO} =in bar.jse mutano la variabile "costante". AFAIK, per imporre l'immutabilità delle esportazioni, sono ancora necessari moduli ES o Object.freeze.
Treno Spagna

Si può anche cambiare FOOdentro foo.js.
lima_fil,

16

Ho trovato la soluzione suggerita da Dominic come la migliore, ma manca ancora una caratteristica della dichiarazione "const". Quando si dichiara una costante in JS con la parola chiave "const", l'esistenza della costante viene verificata al momento dell'analisi, non in fase di esecuzione. Quindi, se hai sbagliato l'ortografia del nome della costante da qualche parte più avanti nel tuo codice, otterrai un errore quando provi ad avviare il tuo programma node.js. Che è un controllo ortografico molto più efficace.

Se definisci la costante con la funzione define () come suggerito da Dominic, non otterrai un errore se hai sbagliato l'ortografia della costante e il valore della costante errata sarà indefinito (il che può portare a debug del mal di testa).

Ma immagino che questo sia il migliore che possiamo ottenere.

Inoltre, ecco una sorta di miglioramento della funzione di Dominic, in constans.js:

global.define = function ( name, value, exportsObject )
{
    if ( !exportsObject )
    {
        if ( exports.exportsObject )
            exportsObject = exports.exportsObject;
        else 
            exportsObject = exports;        
    }

    Object.defineProperty( exportsObject, name, {
        'value': value,
        'enumerable': true,
        'writable': false,
    });
}

exports.exportObject = null;

In questo modo è possibile utilizzare la funzione define () in altri moduli e consente di definire costanti sia all'interno del modulo constants.js che costanti all'interno del modulo da cui è stata chiamata la funzione. La dichiarazione delle costanti del modulo può quindi essere eseguita in due modi (in script.js).

Primo:

require( './constants.js' );

define( 'SOME_LOCAL_CONSTANT', "const value 1", this ); // constant in script.js
define( 'SOME_OTHER_LOCAL_CONSTANT', "const value 2", this ); // constant in script.js

define( 'CONSTANT_IN_CONSTANTS_MODULE', "const value x" ); // this is a constant in constants.js module

Secondo:

constants = require( './constants.js' );

// More convenient for setting a lot of constants inside the module
constants.exportsObject = this;
define( 'SOME_CONSTANT', "const value 1" ); // constant in script.js
define( 'SOME_OTHER_CONSTANT', "const value 2" ); // constant in script.js

Inoltre, se vuoi che la funzione define () sia chiamata solo dal modulo constants (non per gonfiare l'oggetto globale), la definisci così in constants.js:

exports.define = function ( name, value, exportsObject )

e usalo così in script.js:

constants.define( 'SOME_CONSTANT', "const value 1" );

11

Dalla precedente esperienza di progetto, questo è un buon modo:

In constants.js:

// constants.js

'use strict';

let constants = {
    key1: "value1",
    key2: "value2",
    key3: {
        subkey1: "subvalue1",
        subkey2: "subvalue2"
    }
};

module.exports =
        Object.freeze(constants); // freeze prevents changes by users

In main.js (o app.js, ecc.), Usalo come di seguito:

// main.js

let constants = require('./constants');

console.log(constants.key1);

console.dir(constants.key3);

8

penso che const risolva il problema per la maggior parte delle persone che cercano questa risposta. Se hai davvero bisogno di una costante immutabile, guarda le altre risposte. Per mantenere tutto organizzato, salvo tutte le costanti in una cartella e quindi richiedo l'intera cartella.

file src / main.js

const constants = require("./consts_folder");

src / consts_folder / index.js

const deal = require("./deal.js")
const note = require("./note.js")


module.exports = {
  deal,
  note
}

Ps. qui deale notesarà il primo livello su main.js

src / consts_folder / note.js

exports.obj = {
  type: "object",
  description: "I'm a note object"
}

Ps. objsarà di secondo livello su main.js

src / consts_folder / deal.js

exports.str = "I'm a deal string"

Ps. strsarà di secondo livello su main.js

Risultato finale sul file main.js:

console.log(constants.deal); ouput:

{deal: {str: 'I \' ma deal string '},

console.log(constants.note); ouput:

nota: {obj: {tipo: 'oggetto', descrizione: 'I \' ma note object '}}



4

In alternativa, è possibile raggruppare i valori "costanti" in un oggetto locale ed esportare una funzione che restituisce un clone superficiale di questo oggetto.

var constants = { FOO: "foo" }

module.exports = function() {
  return Object.assign({}, constants)
}

Quindi non importa se qualcuno riassegna FOO perché influenzerà solo la sua copia locale.


o solo module.exports = () => ({FOO: "foo", BAR: "bar"});
Björn Grambow,

3

Poiché Node.js utilizza i modelli CommonJS, è possibile condividere le variabili solo tra i moduli con module.exportso impostando una var globale come si farebbe nel browser, ma invece di utilizzare la finestra utilizzata global.your_var = value;.


2

Ho finito per farlo esportando un oggetto congelato con funzioni getter anonime, piuttosto che le costanti stesse. Ciò riduce il rischio di cattivi bug introdotti a causa di un semplice errore di battitura del nome const, poiché verrà generato un errore di runtime in caso di errore di battitura. Ecco un esempio completo che utilizza anche i simboli ES6 per le costanti, garantendo l'univocità e le funzioni della freccia ES6. Gradirei feedback se qualcosa in questo approccio sembra problematico.

'use strict';
const DIRECTORY = Symbol('the directory of all sheets');
const SHEET = Symbol('an individual sheet');
const COMPOSER = Symbol('the sheet composer');

module.exports = Object.freeze({
  getDirectory: () => DIRECTORY,
  getSheet: () => SHEET,
  getComposer: () => COMPOSER
});

0

Ti consiglio di farlo con il webpack (presuppone che tu stia usando il webpack).

Definire le costanti è semplice come impostare il file di configurazione del webpack:

var webpack = require('webpack');
module.exports = {
    plugins: [
        new webpack.DefinePlugin({
            'APP_ENV': '"dev"',
            'process.env': {
                'NODE_ENV': '"development"'
            }
        })
    ],    
};

In questo modo li definisci al di fuori della tua fonte e saranno disponibili in tutti i tuoi file.


0

Non credo sia una buona pratica invadere lo spazio GLOBALE dai moduli, ma in scenari in cui potrebbe essere strettamente necessario implementarlo:

Object.defineProperty(global,'MYCONSTANT',{value:'foo',writable:false,configurable:false});

Deve essere considerato l'impatto di questa risorsa. Senza una corretta denominazione di tali costanti, il rischio di OVERWRITTING già definito variabili globali, è qualcosa di reale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.