Этот вопрос более общий, чем Ваш случай с node.js и даже более общий, чем JavaScript вообще. Хорошо разделить свой код на части, выделить абстракции, дать им названия и объединить их в одно целое - это одна из главных задач программиста на любом языке.
Я сначала отвечу на частный вопрос по Node.js и JavaScript, а потом объясню эту тему глубже. Для Node.js есть
require
, при помощи которого можно импортировать из других модулей. Это реализация
Dependency Lookup
, т.е. мы имеем несколько модулей и каждый из них знает, от каких он зависит и может запросить менеджер зависимостей дать ссылку на то, что экспортирует другой модуль (в данном случае файл). Например, мы можем сделать
require('./matrix.js')
или
require('./lib/matrix.js')
и таким образом получим ссылку на то, что экспортирует
matrix.js
через
module.exports = { ... };
. Пример кода с импортом:
names.js
В этот самый
module.exports
мы отдаем ссылку на функцию ил на объект или массив, который содержит все, что мы хотим экспортировать (можно экспортировать и скалярное значение, но это практически не нужно). В подавляющем большинстве случаев экспортируют объект (используя его как справочник экспортируемых идентификаторов), чуть реже одну функцию (иногда фабрику или функцию обертку) или конструктор прототипа (или класса). Пример кода с экспортом:
lib/submodule2.js
Но можно импортировать зависимости целыми массивами, например тут:
main.js Если посмотреть чуть шире ноды, на JavaScript, то есть способ импорта/экспорта через ключевые слова
import
и
export
. Примеры:
import.mjs и
export.mjs
Документацию модно почитать тут:
1. Модули для Node.js через require/module.exports:
https://nodejs.org/api/modules.html
2. Модули для Node.js через import/export:
https://nodejs.org/api/esm.html
3. Модули для JavaScript через import/export:
https://developer.mozilla.org/en-US/docs/Web/JavaS... и
https://developer.mozilla.org/en-US/docs/Web/JavaS...
Но это все техническая реализация, гораздо важнее то, как мы разбиваем наш код на модули или абстракции. Тут нужно ввести понятие связывание и классифицировать методы связывания (частей кода):
1. Через общие данные (самое жесткое связывание)
2. Через вызовы (обычно между модулями с экспортом и импортом)
3. Через события (самое легкое, между слабосвязанными программными компонентами)
Если из разных функций, прототипов и классов (их методов) Вы обращаетесь к одним и тем же данным, а тем более, меняете их, то это очень плохо. Могут быть общие идентификаторы такие, но это только константы, которые не меняются из разных мест. Могут быть группы функций, которые должны видеть одну переменную и менять ее, но их нужно объединять друг с другом через замыкания и частичное применение. И такие, связанные через данные конструкции (сильно связанные) должны находиться только внутри одного файла. Изменение одних и тех же данных из разных мест, может привести к формированию сложного и непредсказуемого состояния, а в конечном итоге к ситуациям, когда комбинаторный взрыв состояния не может быть адекватно обработан программой, все случаи просто не покрыты условиями и мы даже не можем себе представить и учесть в коде все случаи.
Вызовы - это гораздо более безопасный способ взаимодействия программных компонентов, поэтому мы используем его для интерфейса между модулями и между классами и прототипами. Мы экспортируем/импортируем коллекции функций (API) или коллекции классов, что предполагает, что все взаимодействие между ними будет через вызовы (даже обращение к свойствам может быть перехвачено геттерами и сеттерами). Об этом способе связывания модулей я уже выше много сказал.
Наименее жесткий способ связывания - через события. Тут один программный компонент (модуль, класс, прототип, функтор) может подписаться на события другого или вызывать события у себя или в другого не ожидая, что там есть подписчики. Конечно тут могут быть проблемы с контрактами событий, т.е. именами и форматами данных, они все указаны не жестко и тут может быть много ошибок. Это очень гибкий способ, но он таит в себе потенциальные проблемы совместимости. Так что, где можно обойтись вызовами, то лучше не использовать события.
Есть еще один средний способ между вызовами и событиями, это callback (и listener). При помощи него события и сделаны, практически, в метод
.on(name, callback)
(или его аналоги) передается функция callback в качестве аргумента. На этом способе построено асинхронное программирование, в том числе такие способы связывания, как асинхронная последовательная композиция и асинхронная параллельная композиция функций, чеининг и промисы.