Як ви ділите константи в модулях NodeJS?


239

В даний час я роблю це:

foo.js

const FOO = 5;

module.exports = {
    FOO: FOO
};

І використовуючи це в bar.js:

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

Чи є кращий спосіб зробити це? Незручно оголошувати константу в об'єкті експорту.


6
Якщо ви хочете експортувати його, ви помістіть його в exports. Що незручного в цьому?
Алекс Вейн

5
Я звик до C # і PHP. Напевно, я просто повинен звикнути визначати кожну константу двічі. Може, в майбутньому у нас буде export const FOO = 5;.
Вежа

1
@Tower Майбутнє зараз (ES2015)! 2ality.com/2014/09/…
Іспанія Поїзд

1
Чи це функціонально відрізняється від більш стислого module.exports={FOO:5};?
Джо Лапп

3
Він не тільки відчуває себе аквардним, він уже не є постійним
Іні

Відповіді:


96

Ви можете явно експортувати його в глобальний обсяг за допомогою global.FOO = 5. Тоді вам просто потрібно вимагати файл, і навіть не зберігати своє повернене значення.

Але насправді, не варто цього робити. Зберігати речі правильно капсульованими - це добре. Ви вже маєте правильну ідею, тому продовжуйте робити те, що ви робите.


51
Вибачте за це, але -1 за те, що краще знаю, але не надаю альтернативного (кращого) рішення; (re: "Але насправді, ви не повинні цього робити. Хороша річ утримувати речі в капсулі.")
Дякую

22
Якби вся спільнота розробників програмного забезпечення думала саме так, ми все одно використовувалимете punchards. На щастя, є декілька маверкіків, які знають, коли краще порушити шалені правила, які ми накладаємо на себе. Якщо інкапсуляція корисна, використовуйте її. Якщо нервова няня перестає займатися своєю роботою, звільнити нервову няню і впоратися з цим.
несинхронізований

22
@naomik (дуже пізній час відповіді) Справжня причина, що я не запропонував кращого рішення, полягає в тому, що ОП вже знає рішення. Вкладіть речі у власний модуль і вимагайте їх, де це необхідно.
Алекс Вейн

1
Тоді це не реальна відповідь, а скоріше слід пояснити коментар із твердженням, що "ти робиш досить добре, альтернативи погані" ..
Андрій Попов

1
Неправильне застосування інкапсуляції. Коли клас використовує спеціальні значення в якості індикаторів і дає їм ім'я, ви хочете поділитися цим будь-яким кодом, який використовує цей клас.
grantwparks

314

На мою думку, використання Object.freezeдозволяє отримати сушений і декларативніший стиль. Мій кращий зразок:

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

Застаріле попередження про ефективність

Наступна проблема була виправлена ​​в v8 в січні 2014 року і більше не стосується більшості розробників:

Майте на увазі, що як налаштування, що можна записати на помилкове, так і використовувати Object.freeze, у версії v8 передбачено велике покарання за продуктивність - https://bugs.chromium.org/p/v8/isissue/detail?id=1858 та http://jsperf.com / performance-Заморожений-об'єкт


4
Гарний випадок використання для Object.freeze!
колба Естуса

Як це має виглядати, якщо мені потрібно експортувати як константи, так і функції? Чи варто також помістити функції в блок заморожування?
Том

3
такий підхід кращий, оскільки автозаповнення IDE працює з ним.
Девід А

3
Це чудова відповідь, але це може відвернути людей від такого підходу через застаріле попередження про продуктивність v8 наприкінці. Будь ласка, спробуйте видалити попередження.
sampathsris

4
Дякую @Krumia! Я оновив його, але залишив оригінальний текст попередження лише для історичного контексту (і тому, що деякі з цих коментарів не мали б сенсу без нього).
Іспанія поїзд

163

Технічно constне входить до специфікації ECMAScript. Також, використовуючи вказаний вами шаблон "CommonJS модуль", ви можете змінити значення цієї "константи", оскільки тепер це лише властивість об'єкта. .

Для того, щоб отримати реальну константу, ви можете поділитися, перевірити Object.create, Object.definePropertyі Object.defineProperties. Якщо ви встановите writable: false, значення у вашій "константі" неможливо змінити. :)

Це трохи детально, (але навіть це можна змінити за допомогою невеликого JS), але вам потрібно зробити це лише один раз для вашого модуля констант. Використовуючи ці методи, будь-який атрибут, для якого ви не залишаєте стандартні false. (на відміну від визначення властивостей за допомогою призначення, яке за замовчуванням застосовує всі атрибути true)

Отже, гіпотетично, ви можете просто встановити, valueі enumerable, виходячи з роботи, writableі configurableоскільки вони за замовчуванням false, я щойно включив їх для ясності.

Оновлення - я створив новий модуль ( вузли-константи ) з допоміжними функціями для цього дуже зручного випадку.

constants.js - Добре

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

constants.js - Краще

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 Це не обов'язково, перевірте документацію на Object.defineProperty(). Усі властивості, які не визначені, передбачаються falseв цьому контексті.
Домінік Барнс

6
Також примітно, Object.freeze ()
damianb

1
Це найкраща відповідь на це питання. +1. Якби я міг, я б більше підтримав це.
Райан

1
Чудова відповідь, дуже елегантне та безпечне рішення.
Олексій

1
@SpainTrain Це , як видається, були зафіксовані codereview.chromium.org/135903014
Grinde

100

ES6 спосіб.

експорт у foo.js

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

імпорт у bar.js

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

40
Так. Переповнення стека потребує способу знецінення застарілих відповідей.
Рік Джоллі

7
Зауважте, що саме constв bar.jsтому, що застосовує незмінність деструктурованої змінної, а не constв foo.js. Тобто, можна використовувати let {FOO} =в bar.jsі мутувати «постійну» змінну. AFAIK, щоб забезпечити незмінність експорту, все ще потрібні або модулі ES, або Object.freeze.
Іспанія поїзд

Можна все також змінити FOOвсередині foo.js.
lima_fil

16

Я знайшов рішення, яке Домінік запропонував як найкраще, але воно все ще не вистачає однієї функції декларації "const". Коли ви оголошуєте константу в JS за допомогою ключового слова "const", існування константи перевіряється під час розбору, а не під час виконання. Тож якщо ви неправильно написали ім'я константи десь пізніше у своєму коді, ви отримаєте помилку при спробі запуску програми node.js. Що набагато краще перевірки орфографії.

Якщо ви визначите константу з функцією define (), як запропонував Домінік, ви не отримаєте помилку, якщо помилково написали константу, і значення констант, написане неправильно, буде невизначено (що може призвести до налагодження головних болів).

Але я думаю, це найкраще, що ми можемо отримати.

Крім того, ось своєрідне вдосконалення функції Домініка в 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;

Таким чином ви можете використовувати функцію define () в інших модулях, і вона дозволяє визначати константи як всередині модуля constants.js, так і константи всередині вашого модуля, з якого ви викликали функцію. Оголошення констант модуля може бути зроблено двома способами (у script.js).

Перший:

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

Друге:

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

Крім того, якщо ви хочете, щоб функція define () викликалася тільки з модуля констант (щоб не роздував глобальний об'єкт), ви визначаєте її так у constants.js:

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

і використовувати його так у script.js:

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

11

З попереднього досвіду проекту, це хороший спосіб:

В the 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

У main.js (або app.js тощо) використовуйте його, як показано нижче:

// main.js

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

console.log(constants.key1);

console.dir(constants.key3);

8

Я думаю, що це constвирішує проблему для більшості людей, які шукають цю передчуття. Якщо вам справді потрібна незмінна константа, подивіться на інші відповіді. Щоб все було організовано, я зберігаю всі константи в папці, а потім вимагаю всю папку.

файл 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
}

Пс. тут dealі noteбуде перший рівень на main.js

src / consts_folder / note.js

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

Пс. objбуде другим рівнем на main.js

src / consts_folder / deal.js

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

Пс. strбуде другим рівнем на main.js

Кінцевий результат у файлі main.js:

console.log(constants.deal); Вихід:

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

console.log(constants.note); Вихід:

Примітка: {obj: {type: 'object', description: 'I \' ma note object '}}



4

Як альтернатива, ви можете згрупувати свої "постійні" значення в локальний об'єкт та експортувати функцію, яка повертає мілкий клон цього об'єкта.

var constants = { FOO: "foo" }

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

Тоді не має значення, чи хтось повторно призначить FOO, оскільки це вплине лише на їх локальну копію.


або просто module.exports = () => ({FOO: "foo", BAR: "bar"});
Björn Grambow

3

Оскільки Node.js використовує шаблони CommonJS, ви можете ділитися змінними між модулями лише за module.exportsдопомогою встановлення глобального варіанту var, як у браузері, але замість того, щоб використовувати вікно, яке ви використовуєте global.your_var = value;.


2

Я закінчив це, експортувавши заморожений об'єкт з анонімними функціями отримання, а не самі константи. Це зменшує ризик появи неприємних помилок через простий друк назви const, оскільки помилка виконання буде видана у разі помилки. Ось повний приклад, який також використовує символи ES6 для констант, забезпечуючи унікальність та функції стрілок ES6. Будемо вдячні за відгуки, якщо щось у цьому підході здасться проблематичним.

'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

Я рекомендую робити це з webpack (припускаючи, що ви використовуєте webpack).

Визначити константи так само просто, як встановити файл конфігурації веб-пакету:

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

Таким чином ви визначаєте їх поза своїм джерелом, і вони будуть доступні у всіх ваших файлах.


0

Я не думаю, що це вдала практика вторгнутись у простір GLOBAL від модулів, але в сценаріях, де це може бути суворо необхідне для його реалізації:

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

Слід враховувати вплив цього ресурсу. Без належного іменування цих констант, ризик ЗАПИСУВАННЯ вже визначених глобальних змінних є чимось реальним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.