Jak współdzielić stałe w modułach NodeJS?

239

Obecnie robię to:

foo.js

const FOO = 5;

module.exports = {
    FOO: FOO
};

I używając go w bar.js:

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

Czy jest na to lepszy sposób? Dziwne jest deklarowanie stałej w obiekcie eksportu.

Wieża
źródło
6
Jeśli chcesz go wyeksportować, umieść go w exports. Co jest w tym niewygodnego?
Alex Wayne
5
Jestem przyzwyczajony do C # i PHP. Chyba muszę się przyzwyczaić do definiowania każdej stałej dwa razy. Może w przyszłości będziemy mieli export const FOO = 5;.
Wieża
1
@Tower Przyszłość jest teraz (ES2015)! 2ality.com/2014/09/…
Hiszpania Train
1
Czy to funkcjonalnie różni się od bardziej zwięzłego module.exports={FOO:5};?
Joe Lapp,
3
Nie tylko czuje się nieswojo, ale już nie jest stały
Ini

Odpowiedzi:

96

Możesz jawnie wyeksportować go do zakresu globalnego za pomocą global.FOO = 5. Następnie musisz po prostu zażądać pliku, a nawet nie zapisać wartości zwracanej.

Ale tak naprawdę nie powinieneś tego robić. Utrzymywanie rzeczy właściwie kapsułkowanych to dobra rzecz. Masz już dobry pomysł, więc rób dalej to, co robisz.

Alex Wayne
źródło
51
Przykro mi to robić, ale -1 za lepsze zrozumienie, ale nie zapewnienie alternatywnego (lepszego) rozwiązania; (re: „Ale tak naprawdę nie powinieneś tego robić. Utrzymywanie właściwego kapsułkowania jest dobrą rzeczą.”)
Dziękuję
22
Gdyby cała społeczność programistów myślała w ten sposób, nadal używalibyśmy dziurkaczy. Na szczęście istnieje kilku indywidualistów, którzy wiedzą, kiedy lepiej złamać szalone zasady, które sobie narzucamy. Jeśli kapsułkowanie jest przydatne, użyj go. Jeśli to nerwowa niania powstrzymuje cię przed wykonywaniem pracy, zwolnij nerwową nianię i kontynuuj.
niezsynchronizowany
22
@ anomik (bardzo późny czas odpowiedzi) Prawdziwym powodem, dla którego nie podałem lepszego rozwiązania, jest to, że OP już zna rozwiązanie. Hermetyzuj rzeczy we własnym module i wymagaj ich w razie potrzeby.
Alex Wayne,
1
To nie jest rzeczywista odpowiedź i powinien być raczej komentarzem wyjaśniającym, że „robisz wystarczająco dobrze, alternatywy są złe” ..
Andrey Popov
1
Niewłaściwe zastosowanie kapsułkowania. Kiedy klasa używa specjalnych wartości jako wskaźników i nadaje im nazwę, CHCESZ udostępnić to każdemu kodowi, który korzysta z tej klasy.
grantwparks
314

Moim zdaniem wykorzystanie Object.freezepozwala na DRYer i bardziej deklaratywny styl. Mój preferowany wzór to:

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

Ostrzeżenie o przestarzałej wydajności

Następujący problem został rozwiązany w wersji 8 w styczniu 2014 roku i nie jest już istotny dla większości programistów:

Należy pamiętać, że zarówno ustawienie zapisywalnego na false, jak i użycie Object.freeze ma ogromny wpływ na wydajność w wersji 8 - https://bugs.chromium.org/p/v8/issues/detail?id=1858 i http://jsperf.com / performance-frozen-object

Pociąg hiszpański
źródło
4
Dobry przypadek użycia dla Object.freeze!
Estus Flask
Jak powinno to wyglądać, jeśli muszę wyeksportować zarówno stałe, jak i funkcje? Czy powinienem również umieścić funkcje w bloku zamrożenia?
Tom
3
takie podejście jest lepsze, ponieważ autouzupełnianie IDE działa z nim.
David A
3
To świetna odpowiedź, ale może odwrócić ludzi od tego podejścia z powodu przestarzałego ostrzeżenia o wydajności v8 na końcu. Proszę rozważyć usunięcie ostrzeżenia.
sampathsris
4
Dzięki @Krumia! Zaktualizowałem go, ale pozostawiłem oryginalny tekst ostrzegawczy tylko dla kontekstu historycznego (i ponieważ niektóre z tych komentarzy bez niego nie miałyby sensu).
Pociąg Hiszpanii
163

Technicznie constnie jest częścią specyfikacji ECMAScript. Za pomocą wzorca „Moduł CommonJS”, który zauważyłeś, możesz zmienić wartość tej „stałej”, ponieważ jest ona teraz tylko właściwością obiektu. (nie jestem pewien, czy spowoduje to kaskadowe zmiany w innych skryptach wymagających tego samego modułu, ale jest to możliwe)

Aby uzyskać prawdziwy stałych, które można również podzielić, sprawdź Object.create, Object.definePropertyi Object.defineProperties. Jeśli ustawisz writable: false, wartości w „stałej” nie będzie można zmienić. :)

Jest to trochę gadatliwe (ale nawet to można zmienić za pomocą małego JS), ale powinieneś to zrobić tylko raz dla modułu stałych. Korzystając z tych metod, domyślny jest dowolny atrybut, który pomijasz false. (w przeciwieństwie do definiowania właściwości za pomocą przypisania, które domyślnie przypisuje wszystkie atrybuty true)

Tak więc, hipotetycznie, możesz po prostu ustawić valuei enumerable, pomijając writablei configurableponieważ będą domyślnie ustawione , falsepo prostu uwzględniłem je dla jasności.

Aktualizacja - stworzyłem nowy moduł ( stałe węzłów ) z funkcjami pomocniczymi dla tego bardzo przypadkowego przypadku.

constants.js - Dobrze

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

constants.js - lepiej

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
źródło
2
@AntoineHedgecock Nie jest konieczne, sprawdź dokumentację na Object.defineProperty(). W falsetym kontekście przyjmuje się wszystkie nieokreślone właściwości .
Dominic Barnes
6
Na uwagę zasługuje również Object.freeze ()
damianb,
1
To najlepsza odpowiedź na to pytanie. +1. Gdybym mógł, głosowałbym bardziej.
Ryan
1
Cudowna odpowiedź, bardzo eleganckie i bezpieczne rozwiązanie.
Alex
1
@SpainTrain Wygląda na to, że zostało to naprawione przez codereview.chromium.org/135903014
Grinde
100

Sposób ES6.

eksport do foo.js

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

importuj w bar.js

const {FOO} = require('foo');
Diego Mello
źródło
40
Tak. Przepełnienie stosu wymaga sposobu na utratę wartości nieaktualnych odpowiedzi.
Rick Jolly,
7
Zauważ, że to constw bar.jstym wymusza niezmienność zniszczonej zmiennej, a nie constw foo.js. Oznacza to, że można używać let {FOO} =w bar.jsi mutować zmienną „stały”. AFAIK, aby wymusić niezmienność eksportu, nadal potrzebne są albo moduły ES, albo Object.freeze.
Pociąg Hiszpanii
Można również zmienić w FOOśrodku foo.js.
lima_fil,
16

Znalazłem rozwiązanie, które Dominic zaproponował jako najlepsze, ale wciąż brakuje mu jednej cechy deklaracji „const”. Gdy deklarujesz stałą w JS za pomocą słowa kluczowego „const”, istnienie stałej jest sprawdzane w czasie analizy, a nie w czasie wykonywania. Więc jeśli źle wpisałeś nazwę stałej gdzieś w kodzie później, pojawi się błąd podczas próby uruchomienia programu node.js. Co jest znacznie lepszym sprawdzaniem pisowni.

Jeśli zdefiniujesz stałą za pomocą funkcji defin (), jak sugerował Dominic, nie dostaniesz błędu, jeśli źle przeliterujesz stałą, a wartość błędnie stałej będzie niezdefiniowana (co może prowadzić do debugowania bólów głowy).

Ale myślę, że to najlepsze, co możemy dostać.

Dodatkowo, oto rodzaj ulepszenia funkcji Dominica w 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;

W ten sposób możesz użyć funkcji defin () w innych modułach i pozwala ona zdefiniować stałe zarówno wewnątrz modułu constants.js, jak i stałe w module, z którego wywołałeś funkcję. Deklarowanie stałych modułu można następnie wykonać na dwa sposoby (w script.js).

Pierwszy:

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

Druga:

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

Ponadto, jeśli chcesz, aby funkcja definiować () była wywoływana tylko z modułu stałych (nie nadmuchując obiektu globalnego), zdefiniuj ją w następujący sposób w constants.js:

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

i użyj go w następujący sposób w script.js:

constants.define( 'SOME_CONSTANT', "const value 1" );
xmak
źródło
11

Z wcześniejszych doświadczeń projektowych jest to dobry sposób:

W pliku 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

W main.js (lub app.js itp.) Użyj go w następujący sposób:

// main.js

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

console.log(constants.key1);

console.dir(constants.key3);
Manohar Reddy Poreddy
źródło
8

Myślę, że to constrozwiązuje problem większości osób szukających tej odpowiedzi. Jeśli naprawdę potrzebujesz niezmiennej stałej, spójrz na inne odpowiedzi. Aby wszystko uporządkować, zapisuję wszystkie stałe w folderze, a następnie wymagam całego folderu.

plik 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. tutaj deali notebędzie pierwszy poziom na main.js

src / consts_folder / note.js

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

Ps. objbędzie drugi poziom na main.js

src / consts_folder / deal.js

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

Ps. strbędzie drugi poziom na main.js

Ostateczny wynik w pliku main.js:

console.log(constants.deal); Ouput:

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

console.log(constants.note); Ouput:

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

Luis Martins
źródło
4

Alternatywnie możesz pogrupować swoje „stałe” wartości w obiekcie lokalnym i wyeksportować funkcję, która zwraca płytki klon tego obiektu.

var constants = { FOO: "foo" }

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

Wtedy nie ma znaczenia, czy ktoś ponownie przydzieli FOO, ponieważ wpłynie to tylko na jego lokalną kopię.

herman
źródło
lub po prostu module.exports = () => ({FOO: „foo”, BAR: „bar”});
Björn Grambow
3

Ponieważ Node.js używa wzorców CommonJS, możesz udostępniać zmienne tylko między modułami za pomocą module.exportslub ustawiając zmienną globalną tak jak w przeglądarce, ale zamiast używać okna, którego używasz global.your_var = value;.

alessioalex
źródło
2

Skończyło się na tym, że wyeksportowałem zamrożony obiekt z anonimowymi funkcjami pobierającymi, a nie same stałe. Zmniejsza to ryzyko wprowadzenia nieprzyjemnych błędów z powodu zwykłej literówki w nazwie stałej, ponieważ błąd wykonawczy zostanie wygenerowany w przypadku literówki. Oto pełny przykład, który używa również symboli ES6 dla stałych, zapewniając unikalność i funkcje strzałek ES6. Byłbym wdzięczny za informację zwrotną, jeśli coś w tym podejściu wydaje się problematyczne.

'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
});
Elokwencja
źródło
0

Zalecam robienie tego z webpackiem (zakładam, że używasz webpacka).

Definiowanie stałych jest tak proste, jak ustawienie pliku konfiguracyjnego pakietu WWW:

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

W ten sposób zdefiniujesz je poza swoim źródłem i będą one dostępne we wszystkich twoich plikach.

galki
źródło
0

Nie sądzę, że dobrą praktyką jest inwazja GLOBALNEJ przestrzeni z modułów, ale w scenariuszach, w których może to być absolutnie konieczne do jej wdrożenia:

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

Należy wziąć pod uwagę wpływ tego zasobu. Bez odpowiedniego nazewnictwa tych stałych ryzyko NADPISYWANIA już zdefiniowanych zmiennych globalnych jest czymś realnym.

colxi
źródło