Bagaimana Anda berbagi konstanta dalam modul NodeJS?

240

Saat ini saya sedang melakukan ini:

foo.js

const FOO = 5;

module.exports = {
    FOO: FOO
};

Dan menggunakannya dalam bar.js:

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

Apakah ada cara yang lebih baik untuk melakukan ini? Rasanya canggung untuk mendeklarasikan konstanta pada objek ekspor.

Menara
sumber
6
Jika Anda ingin mengekspornya, Anda memasukkannya ke dalam exports. Apa yang canggung tentang itu?
Alex Wayne
5
Saya sudah terbiasa dengan C # dan PHP. Saya kira saya hanya harus terbiasa mendefinisikan setiap konstanta dua kali. Mungkin di masa depan kita akan memilikinya export const FOO = 5;.
Tower
1
@Tower Masa depan sekarang (ES2015)! 2ality.com/2014/09/…
Spain Train
1
Apakah ini secara fungsional berbeda dari yang lebih ringkas module.exports={FOO:5};?
Joe Lapp
3
Itu tidak hanya terasa akward, itu tidak konstan lagi
Ini

Jawaban:

97

Anda dapat secara eksplisit mengekspornya ke lingkup global dengan global.FOO = 5. Maka Anda hanya perlu memerlukan file tersebut, dan bahkan tidak menyimpan nilai pengembalian Anda.

Tapi sungguh, Anda tidak harus melakukan itu. Menyimpan segala sesuatu dengan benar adalah hal yang baik. Anda sudah memiliki ide yang tepat, jadi terus lakukan apa yang Anda lakukan.

Alex Wayne
sumber
51
Saya minta maaf untuk melakukan ini, tetapi -1 untuk mengetahui lebih baik tetapi tidak memberikan solusi alternatif (lebih baik); (re: "Tapi sungguh, Anda tidak harus melakukan itu. Menjaga hal-hal yang dikemas dengan benar adalah hal yang baik.")
Terima kasih
22
Jika seluruh komunitas pengembangan perangkat lunak berpikir seperti ini, kami masih akan menggunakan punchard. Untungnya ada beberapa maverick di luar sana yang tahu kapan lebih baik untuk melanggar aturan gila yang kita buat pada diri kita sendiri. Jika enkapsulasi bermanfaat, gunakan itu. Jika pengasuh yang gugup menghentikan Anda melakukan pekerjaan Anda, pecat pengasuh yang gugup dan lanjutkan.
tidak disinkronkan
22
@naomik (waktu balasan sangat terlambat) Alasan sebenarnya saya tidak memberikan solusi yang lebih baik adalah karena OP sudah mengetahui solusinya. Meringkas hal-hal dalam modul mereka sendiri dan meminta mereka jika perlu.
Alex Wayne
1
Maka ini bukan jawaban yang sebenarnya dan lebih baik menjadi komentar penjelasan yang menyatakan "Anda melakukan cukup baik, alternatif buruk" ..
Andrey Popov
1
Aplikasi enkapsulasi yang salah. Ketika sebuah kelas menggunakan nilai-nilai khusus sebagai indikator dan memberi mereka nama, Anda INGIN membagikannya dengan kode apa pun yang menggunakan kelas itu.
Grantwparks
314

Menurut pendapat saya, menggunakan Object.freezememungkinkan untuk gaya KERING dan lebih deklaratif. Pola pilihan saya adalah:

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

Peringatan Performa yang sudah usang

Masalah berikut ini diperbaiki di v8 pada Jan 2014 dan tidak lagi relevan untuk sebagian besar pengembang:

Sadarilah bahwa kedua pengaturan dapat ditulisi ke false dan menggunakan Object.freeze memiliki penalti kinerja besar-besaran di v8 - https://bugs.chromium.org/p/v8/issues/detail?id=1858 dan http://jsperf.com / performance-frozen-object

Kereta Spanyol
sumber
4
Kasing penggunaan yang bagus untuk Object.freeze!
Estus Flask
Seperti apa seharusnya jika saya perlu mengekspor konstanta dan fungsi? Haruskah saya meletakkan fungsi di blok beku juga?
Tom
3
pendekatan ini lebih baik karena IDE autocomplete berfungsi dengannya.
David A
3
Ini adalah jawaban yang bagus, tetapi mungkin membuat orang menjauh dari pendekatan ini karena peringatan usang tentang kinerja v8 pada akhirnya. Harap pertimbangkan untuk menghapus peringatan.
sampathsris
4
Terima kasih @Krumia! Saya telah memperbaruinya, tetapi meninggalkan teks peringatan asli hanya untuk konteks historis (dan karena beberapa komentar ini tidak masuk akal tanpa itu).
Kereta Spanyol
163

Secara teknis, constini bukan bagian dari spesifikasi ECMAScript. Juga, menggunakan pola "Modul CommonJS" yang telah Anda catat, Anda dapat mengubah nilai "konstan" itu karena sekarang hanya properti objek. (tidak yakin apakah itu akan menyebabkan perubahan pada skrip lain yang memerlukan modul yang sama, tetapi itu mungkin)

Untuk mendapatkan konstan nyata bahwa Anda juga dapat berbagi, memeriksa Object.create, Object.definePropertydan Object.defineProperties. Jika Anda menetapkan writable: false, maka nilai dalam "konstan" Anda tidak dapat dimodifikasi. :)

Ini sedikit bertele-tele, (tetapi bahkan itu dapat diubah dengan sedikit JS) tetapi Anda hanya perlu melakukannya sekali untuk modul konstanta Anda. Dengan menggunakan metode ini, atribut apa pun yang Anda biarkan default false. (sebagai lawan mendefinisikan properti melalui penugasan, yang default semua atribut untuk true)

Jadi, secara hipotesis, Anda bisa mengatur valuedan enumerable, meninggalkan writabledan configurablekarena mereka akan default false, saya baru saja memasukkan mereka untuk kejelasan.

Perbarui - Saya telah membuat modul baru ( konstanta-simpul ) dengan fungsi pembantu untuk kasus penggunaan yang sangat ini.

constants.js - Bagus

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

constants.js - Lebih baik

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
sumber
2
@AntoineHedgecock Tidak perlu, periksa dokumentasi di Object.defineProperty(). Semua properti yang tidak ditentukan diasumsikan falsedalam konteks ini.
Dominic Barnes
6
Juga patut dicatat, Object.freeze ()
damianb
1
Ini adalah jawaban terbaik dari pertanyaan ini. +1. Jika saya bisa, saya akan lebih memilihnya.
Ryan
1
Jawaban yang bagus, solusi yang sangat elegan dan aman.
Alex
1
@SpainTrain Ini sepertinya sudah diperbaiki oleh codereview.chromium.org/135903014
Grinde
100

Cara ES6.

ekspor dalam foo.js

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

impor di bar.js

const {FOO} = require('foo');
Diego Mello
sumber
40
Iya. Stack Overflow membutuhkan cara untuk mendepresiasi jawaban yang sudah ketinggalan zaman.
Rick Jolly
7
Perhatikan bahwa itu adalah constin bar.jsyang memberlakukan immutabilitas variabel yang dirusak, bukan constin foo.js. Artinya, satu dapat menggunakan let {FOO} =di bar.jsdan bermutasi "konstan" variabel. AFAIK, untuk menegakkan imutabilitas ekspor, kita masih membutuhkan modul ES atau Object.freeze.
Kereta Spanyol
Satu juga bisa berubah FOOdi dalam foo.js.
lima_fil
16

Saya menemukan solusi yang disarankan Dominic untuk menjadi yang terbaik, tetapi masih melewatkan satu fitur dari deklarasi "const". Ketika Anda mendeklarasikan konstanta di JS dengan kata kunci "const", keberadaan konstanta diperiksa pada waktu parse, bukan pada saat runtime. Jadi jika Anda salah mengeja nama konstanta di suatu tempat nanti dalam kode Anda, Anda akan mendapatkan kesalahan ketika Anda mencoba memulai program node.js Anda. Yang merupakan pemeriksaan kesalahan pengejaan yang jauh lebih baik.

Jika Anda mendefinisikan konstanta dengan fungsi define () seperti yang disarankan Dominic, Anda tidak akan mendapatkan kesalahan jika Anda salah mengeja konstanta, dan nilai konstanta salah eja akan tidak terdefinisi (yang dapat menyebabkan sakit kepala debugging).

Tapi saya kira ini adalah yang terbaik yang bisa kita dapatkan.

Selain itu, ini adalah semacam peningkatan fungsi Dominic, di 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;

Dengan cara ini Anda dapat menggunakan fungsi define () di modul lain, dan memungkinkan Anda untuk mendefinisikan konstanta di dalam module constants.js dan konstanta di dalam modul tempat Anda memanggil fungsi tersebut. Mendeklarasikan konstanta modul kemudian dapat dilakukan dengan dua cara (dalam script.js).

Pertama:

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

Kedua:

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

Juga, jika Anda ingin fungsi define () dipanggil hanya dari modul konstanta (bukan untuk mengasapi objek global), Anda mendefinisikannya seperti ini di constants.js:

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

dan gunakan seperti ini di script.js:

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

Dari pengalaman proyek sebelumnya, ini adalah cara yang baik:

Dalam 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

Di main.js (atau app.js, dll.), Gunakan seperti di bawah ini:

// main.js

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

console.log(constants.key1);

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

Saya pikir itu const memecahkan masalah bagi kebanyakan orang yang mencari anwwer ini. Jika Anda benar-benar membutuhkan konstanta abadi, lihat jawaban yang lain. Agar semuanya teratur, saya menyimpan semua konstanta pada folder dan kemudian meminta seluruh folder.

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. di sini dealdan noteakan menjadi level pertama di main.js

src / consts_folder / note.js

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

Ps. objakan menjadi level kedua di main.js

src / consts_folder / deal.js

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

Ps. strakan menjadi level kedua di main.js

Hasil akhir pada file main.js:

console.log(constants.deal); Ouput:

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

console.log(constants.note); Ouput:

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

Luis Martins
sumber
4

Sebagai alternatif, Anda bisa mengelompokkan nilai "konstan" Anda di objek lokal, dan mengekspor fungsi yang mengembalikan klon dangkal objek ini.

var constants = { FOO: "foo" }

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

Maka tidak masalah jika seseorang menetapkan ulang FOO karena hanya akan mempengaruhi salinan lokal mereka.

herman
sumber
atau hanya module.exports = () => ({FOO: "foo", BAR: "bar"});
Björn Grambow
3

Karena Node.js menggunakan pola CommonJS, Anda hanya dapat berbagi variabel antara modul dengan module.exportsatau dengan mengatur var global seperti yang Anda lakukan di browser, tetapi alih-alih menggunakan jendela yang Anda gunakan global.your_var = value;.

alessioalex
sumber
2

Saya akhirnya melakukan ini dengan mengekspor objek beku dengan fungsi pengambil anonim, daripada konstanta itu sendiri. Ini mengurangi risiko bug jahat yang diperkenalkan karena kesalahan ketik sederhana dari nama const, karena kesalahan runtime akan dilemparkan jika terjadi kesalahan ketik. Berikut adalah contoh lengkap yang juga menggunakan Simbol ES6 untuk konstanta, memastikan keunikan, dan fungsi panah ES6. Akan menghargai umpan balik jika ada sesuatu dalam pendekatan ini yang tampaknya bermasalah.

'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
});
Kelancaran berbicara
sumber
0

Saya sarankan melakukannya dengan webpack (menganggap Anda menggunakan webpack).

Menentukan konstanta sesederhana mengatur file konfigurasi webpack:

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

Dengan cara ini Anda mendefinisikannya di luar sumber Anda, dan mereka akan tersedia di semua file Anda.

galki
sumber
0

Saya pikir ini bukan praktik yang baik untuk menyerbu ruang GLOBAL dari modul, tetapi dalam skenario di mana bisa sangat diperlukan untuk mengimplementasikannya:

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

Itu harus dipertimbangkan dampak dari sumber daya ini. Tanpa penamaan konstanta yang tepat, risiko OVERWRITTING sudah didefinisikan variabel global, adalah sesuatu yang nyata.

colxi
sumber