Как вы делитесь константами в модулях NodeJS?

240

В настоящее время я делаю это:

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
@ Башня Будущее сейчас (ES2015)! 2ality.com/2014/09/…
Поезд Испании
1
Отличается ли это функционально от более краткого module.exports={FOO:5};?
Джо Лапп
3
Он не только чувствует себя неловко, он больше не является постоянным
Ини

Ответы:

97

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

Но на самом деле, вы не должны этого делать. Правильно хранить вещи в инкапсуляции. У вас уже есть правильная идея, поэтому продолжайте делать то, что делаете.

Алекс Уэйн
источник
51
Прошу прощения за это, но -1 за лучшее знание, но не за альтернативное (лучшее) решение; (re: «Но на самом деле, вы не должны этого делать. Хорошая инкапсуляция вещей - это хорошо».)
Спасибо,
22
Если бы все сообщество разработчиков программного обеспечения думало так, мы все равно использовали бы перфарды. К счастью, есть несколько индивидуалистов, которые знают, когда лучше нарушать безумные правила, которые мы навязываем себе. Если инкапсуляция полезна, используйте ее. Если нервная няня останавливает вас, делайте свою работу, увольняйте нервную няню и продолжайте в том же духе.
несинхронизировано
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 года и больше не актуальна для большинства разработчиков:

Имейте в виду, что оба параметра, доступные для записи в false, и использование Object.freeze приводят к значительному снижению производительности в v8 - https://bugs.chromium.org/p/v8/issues/detail?id=1858 и http://jsperf.com. / производительность заморозка-объект

Поезд Испании
источник
4
Хороший вариант использования для Object.freeze!
Настой Эстус
Как это должно выглядеть, если мне нужно экспортировать как константы, так и функции? Должен ли я поставить функции в блоке заморозки тоже?
Том
3
этот подход лучше, потому что автозаполнение IDE работает с ним.
Дэвид
3
Это отличный ответ, но он может отвлечь людей от этого подхода из-за устаревшего предупреждения о производительности v8 в конце. Пожалуйста, рассмотрите возможность удаления предупреждения.
Сампатрисрис
4
Спасибо @Krumia! Я обновил его, но оставил исходный текст предупреждения только для исторического контекста (и потому что некоторые из этих комментариев не имели бы смысла без него).
Поезд Испании
163

Технически, constне является частью спецификации ECMAScript. Кроме того, используя шаблон «CommonJS Module», который вы заметили, вы можете изменить значение этой «константы», поскольку теперь это просто свойство объекта. (не уверен, что это приведет к каскадным изменениям в других скриптах, для которых требуется тот же модуль, но это возможно)

Для того, чтобы получить реальную константу, вы можете поделиться, проверить 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
101

ES6 кстати.

экспорт в foo.js

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

импорт в bar.js

const {FOO} = require('foo');
Диего Мелло
источник
41
Да. Переполнение стека нуждается в способе устареть устаревшие ответы.
Рик Джолли
7
Обратите внимание, что именно constin bar.jsобеспечивает неизменяемость переменной, а не constin 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" );
xmak
источник
11

Из предыдущего опыта проекта, это хороший способ:

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

Ps. здесь dealиnote будет первый уровень на main.js

SRC / consts_folder / note.js

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

Ps. objбудет второй уровень на main.js

SRC / consts_folder / deal.js

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

Ps. strбудет второй уровень на main.js

Окончательный результат в файле main.js:

console.log(constants.deal); Ouput:

{сделка: {стр: 'Я сделаю строку'},

console.log(constants.note); Ouput:

примечание: {obj: {тип: 'объект', описание: 'Я \' объект примечания '}}

Луис Мартинс
источник
4

В качестве альтернативы вы можете сгруппировать ваши «постоянные» значения в локальный объект и экспортировать функцию, которая возвращает поверхностный клон этого объекта.

var constants = { FOO: "foo" }

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

Тогда не имеет значения, если кто-то переназначит FOO, потому что это повлияет только на их локальную копию.

Герман
источник
или просто module.exports = () => ({FOO: "foo", BAR: "bar"});
Бьорн
3

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

alessioalex
источник
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:

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

Таким образом, вы определяете их вне вашего источника, и они будут доступны во всех ваших файлах.

galki
источник
0

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

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

Надо учитывать влияние этого ресурса. Без надлежащего именования этих констант риск ПЕРЕЗАПИСИ уже определенных глобальных переменных является чем-то реальным.

colxi
источник