Wie teilen Sie Konstanten in NodeJS-Modulen?

238

Derzeit mache ich das:

foo.js

const FOO = 5;

module.exports = {
    FOO: FOO
};

Und mit bar.js:

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

Gibt es einen besseren Weg, dies zu tun? Es ist unangenehm, die Konstante im Exportobjekt zu deklarieren.

Turm
quelle
6
Wenn Sie es exportieren möchten, legen Sie es in der exports. Was ist daran unangenehm?
Alex Wayne
5
Ich bin an C # und PHP gewöhnt. Ich muss mich wohl nur daran gewöhnen, jede Konstante zweimal zu definieren. Vielleicht haben wir in Zukunft export const FOO = 5;.
Turm
1
@Tower Die Zukunft ist jetzt (ES2015)! 2ality.com/2014/09/…
Spanien Zug
1
Unterscheidet sich dies funktional von dem prägnanteren module.exports={FOO:5};?
Joe Lapp
3
Es fühlt sich nicht nur akward an, es ist keine Konstante mehr
Ini

Antworten:

95

Sie können es explizit mit in den globalen Bereich exportieren global.FOO = 5. Dann müssen Sie nur noch die Datei benötigen und nicht einmal Ihren Rückgabewert speichern.

Aber das solltest du wirklich nicht tun. Es ist eine gute Sache, die Dinge richtig eingekapselt zu halten. Sie haben bereits die richtige Idee, machen Sie also weiter, was Sie tun.

Alex Wayne
quelle
51
Es tut mir leid, dies zu tun, aber -1, weil ich es besser weiß, aber keine alternative (bessere) Lösung anbiete; (Betreff: "Aber wirklich, das sollten Sie nicht tun. Es ist eine gute Sache, die Dinge richtig gekapselt zu halten.")
Vielen Dank,
22
Wenn die gesamte Softwareentwicklungs-Community so denken würde, würden wir immer noch Punchards verwenden. Glücklicherweise gibt es ein paar Außenseiter, die wissen, wann es besser ist, die wahnsinnigen Regeln zu brechen, die wir uns selbst auferlegen. Wenn die Kapselung nützlich ist, verwenden Sie sie. Wenn es ein nervöses Kindermädchen ist, das Sie daran hindert, Ihren Job zu machen, entlassen Sie das nervöse Kindermädchen und machen Sie weiter.
synchronisiert
22
@naomik (super späte Antwortzeit) Der wahre Grund, warum ich keine bessere Lösung angeboten habe, ist, dass das OP die Lösung bereits kennt. Kapsele Dinge in ihrem eigenen Modul und benötige sie bei Bedarf.
Alex Wayne
1
Dann ist dies keine tatsächliche Antwort und sollte eher ein Erklärungskommentar sein, der besagt: "Du machst es gut genug, Alternativen sind schlecht".
Andrey Popov
1
Falsche Anwendung der Einkapselung. Wenn eine Klasse spezielle Werte als Indikatoren verwendet und ihnen einen Namen gibt, möchten Sie dies mit jedem Code teilen, der diese Klasse verwendet.
Grantwparks
313

Meiner Meinung nach Object.freezeermöglicht die Verwendung einen trockeneren und deklarativeren Stil. Mein bevorzugtes Muster ist:

./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'

Veraltete Leistungswarnung

Das folgende Problem wurde im Januar 2014 in Version 8 behoben und ist für die meisten Entwickler nicht mehr relevant:

Beachten Sie, dass sowohl die Einstellung false als auch die Verwendung von Object.freeze in Version 8 - https://bugs.chromium.org/p/v8/issues/detail?id=1858 und http://jsperf.com - einen massiven Leistungsverlust zur Folge haben / Performance-Frozen-Objekt

Spanien Zug
quelle
4
Guter Anwendungsfall für Object.freeze!
Estus Flask
Wie sollte es aussehen, wenn ich sowohl Konstanten als auch Funktionen exportieren muss? Soll ich auch Funktionen in den Freeze-Block einfügen?
Tom
3
Dieser Ansatz ist besser, da die automatische Vervollständigung der IDE damit funktioniert.
David A
3
Dies ist eine großartige Antwort, aber sie könnte die Leute von diesem Ansatz abbringen, da am Ende die Warnung vor der Leistung von v8 veraltet ist. Bitte entfernen Sie die Warnung.
Sampathsris
4
Danke @Krumia! Ich habe es aktualisiert, aber den ursprünglichen Warnungstext nur für den historischen Kontext belassen (und weil einige dieser Kommentare ohne ihn keinen Sinn ergeben würden).
Spanien Zug
163

Technisch gesehen constist nicht Teil der ECMAScript-Spezifikation. Mithilfe des von Ihnen notierten Musters "CommonJS-Modul" können Sie den Wert dieser "Konstante" ändern, da es sich jetzt nur noch um eine Objekteigenschaft handelt. (Ich bin mir nicht sicher, ob dadurch Änderungen an anderen Skripten kaskadiert werden, für die dasselbe Modul erforderlich ist, aber es ist möglich.)

Um eine reelle Konstante zu erhalten , dass Sie können auch teilen Besuche Object.create, Object.definePropertyund Object.defineProperties. Wenn Sie festlegen writable: false, kann der Wert in Ihrer "Konstante" nicht geändert werden. :) :)

Es ist ein wenig ausführlich (aber auch das kann mit ein wenig JS geändert werden), aber Sie sollten es für Ihr Konstantenmodul nur einmal tun müssen. Mit diesen Methoden wird standardmäßig jedes Attribut verwendet, das Sie weglassen false. (im Gegensatz zum Definieren von Eigenschaften über die Zuweisung, bei der standardmäßig alle Attribute verwendet werden true)

Also, hypothetisch, könnte man einfach eingestellt valueund enumerable, Weglassen writableund configurableda sie auf dem Standard würden false, habe ich sie nur für Klarheit enthalten.

Update - Ich habe ein neues Modul ( Knotenkonstanten ) mit Hilfsfunktionen für diesen Anwendungsfall erstellt.

constants.js - Gut

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

constants.js - Besser

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
Dominic Barnes
quelle
2
@AntoineHedgecock Es ist nicht notwendig, überprüfen Sie die Dokumentation auf Object.defineProperty(). falseIn diesem Zusammenhang werden alle nicht angegebenen Eigenschaften angenommen .
Dominic Barnes
6
Ebenfalls bemerkenswert ist Object.freeze ()
Damianb
1
Dies ist die beste Antwort auf diese Frage. +1. Wenn ich könnte, würde ich es mehr unterstützen.
Ryan
1
Wunderbare Antwort, eine sehr elegante und sichere Lösung.
Alex
1
@SpainTrain Dies scheint von codereview.chromium.org/135903014
Grinde
100

ES6 Weg.

Export in foo.js.

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

Import in bar.js

const {FOO} = require('foo');
Diego Mello
quelle
40
Ja. Der Stapelüberlauf benötigt eine Möglichkeit, veraltete Antworten abzuschreiben.
Rick Jolly
7
Beachten Sie, dass es das constIn ist bar.js, das die Unveränderlichkeit der destrukturierten Variablen erzwingt, nicht das constIn foo.js. Das heißt, kann man verwenden , let {FOO} =in bar.jsund mutieren die „Konstante“ Variable. AFAIK, um die Unveränderlichkeit der Exporte durchzusetzen, benötigt man noch entweder ES-Module oder Object.freeze.
Spanien Zug
Man kann sich auch FOOinnen ändern foo.js.
lima_fil
16

Ich fand die von Dominic vorgeschlagene Lösung die beste, aber es fehlt immer noch eine Funktion der "const" -Deklaration. Wenn Sie eine Konstante in JS mit dem Schlüsselwort "const" deklarieren, wird das Vorhandensein der Konstante zur Analysezeit und nicht zur Laufzeit überprüft. Wenn Sie also den Namen der Konstante irgendwo später in Ihrem Code falsch geschrieben haben, wird eine Fehlermeldung angezeigt, wenn Sie versuchen, Ihr Programm node.js zu starten. Welches ist eine weitaus bessere Rechtschreibfehlerprüfung.

Wenn Sie die Konstante mit der Funktion define () definieren, wie von Dominic vorgeschlagen, wird kein Fehler angezeigt, wenn Sie die Konstante falsch geschrieben haben, und der Wert der falsch geschriebenen Konstante ist undefiniert (was zum Debuggen von Kopfschmerzen führen kann).

Aber ich denke, das ist das Beste, was wir bekommen können.

Außerdem gibt es hier eine Art Verbesserung der Dominic-Funktion 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;

Auf diese Weise können Sie die Funktion define () in anderen Modulen verwenden und Konstanten sowohl innerhalb des Moduls constants.js als auch innerhalb Ihres Moduls definieren, von dem aus Sie die Funktion aufgerufen haben. Das Deklarieren von Modulkonstanten kann dann auf zwei Arten erfolgen (in script.js).

Zuerst:

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

Zweite:

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

Wenn Sie möchten, dass die Funktion define () nur vom Konstantenmodul aufgerufen wird (um das globale Objekt nicht aufzublähen), definieren Sie sie wie folgt in constants.js:

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

und benutze es so in script.js:

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

Aus früheren Projekterfahrungen ist dies ein guter Weg:

In den 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

Verwenden Sie es in main.js (oder app.js usw.) wie folgt:

// main.js

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

console.log(constants.key1);

console.dir(constants.key3);
Manohar Reddy Poreddy
quelle
8

Ich denke, das constlöst das Problem für die meisten Leute, die nach dieser Antwort suchen. Wenn Sie wirklich eine unveränderliche Konstante benötigen, schauen Sie sich die anderen Antworten an. Um alles organisiert zu halten, speichere ich alle Konstanten in einem Ordner und benötige dann den gesamten Ordner.

src / main.js Datei

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. hier das dealund notewird die erste Ebene auf der main.js sein

src / consts_folder / note.js

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

Ps. objwird die zweite Ebene auf der main.js sein

src / consts_folder / deal.js

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

Ps. strwird die zweite Ebene auf der main.js sein

Endergebnis in der Datei main.js:

console.log(constants.deal); Ausgabe:

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

console.log(constants.note); Ausgabe:

note: {obj: {type: 'object', description: 'I \' ma note object '}}

Luis Martins
quelle
4

Alternativ können Sie Ihre "konstanten" Werte in einem lokalen Objekt gruppieren und eine Funktion exportieren, die einen flachen Klon dieses Objekts zurückgibt.

var constants = { FOO: "foo" }

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

Dann spielt es keine Rolle, ob jemand FOO neu zuweist, da dies nur die lokale Kopie betrifft.

ihr Mann
quelle
oder einfach module.exports = () => ({FOO: "foo", BAR: "bar"});
Björn Grambow
3

Da Node.js die CommonJS-Muster verwendet, können Sie Variablen nur zwischen Modulen mit module.exportsoder durch Festlegen einer globalen Variablen wie im Browser freigeben, aber anstatt das von Ihnen verwendete Fenster zu verwenden global.your_var = value;.

Alessioalex
quelle
2

Am Ende habe ich ein eingefrorenes Objekt mit anonymen Getter-Funktionen exportiert und nicht die Konstanten selbst. Dies verringert das Risiko böser Fehler, die aufgrund eines einfachen Tippfehlers des const-Namens auftreten, da bei einem Tippfehler ein Laufzeitfehler ausgelöst wird. Hier ist ein vollständiges Beispiel, das auch ES6-Symbole für die Konstanten verwendet, um die Eindeutigkeit sicherzustellen, sowie ES6-Pfeilfunktionen. Würde mich über Feedback freuen, wenn etwas in diesem Ansatz problematisch erscheint.

'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
});
Beredsamkeit
quelle
0

Ich empfehle es mit Webpack (vorausgesetzt, Sie verwenden Webpack).

Das Definieren von Konstanten ist so einfach wie das Festlegen der Webpack-Konfigurationsdatei:

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

Auf diese Weise definieren Sie sie außerhalb Ihrer Quelle und sie sind in allen Ihren Dateien verfügbar.

galki
quelle
0

Ich denke nicht, dass es eine gute Praxis ist, aus Modulen in den GLOBAL-Bereich einzudringen, aber in Szenarien, in denen die Implementierung unbedingt erforderlich sein könnte:

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

Es muss die Auswirkung dieser Ressource berücksichtigt werden. Ohne die richtige Benennung dieser Konstanten ist das Risiko, bereits definierte globale Variablen zu überschreiben, etwas Reales.

colxi
quelle