Node.js, Require и Exports

http://openmymind.net/2012/2/3/Node-Require-and-Exports/
  • Перевод
Когда я только-только начал играться с Node.js, для меня существовала лишь одна вещь, которая доставляла мне дискомфорт. Занятно, но я говорю сейчас о module.exports. Говоря занятно, я намекаю на то, что это, всё-таки, одна из фундаментальных частей Node.js и она достаточно проста. Сейчас же, оглядываясь назад, я не могу объяснить почему меня это так стопорило… Просто помню, что этот момент был для меня не очевиден. Ну и, полагаю, что я один из тех многих, кто встретив его разок-другой, сначала только путался, прежде чем написал что-либо применив его.

В Node, все штуки видны друг другу только в рамках одного и того же файла. Под штуками я подразумеваю переменные, функции, классы и их члены. Например у нас есть файл misc.js следующего содержания:

var x = 5;
var addX = function(value) {
  return value + x;
};

Привычный доступ к переменной x и функции addX из другого файла невозможен. Это никак не связано с использованием var. Дело в том, что Node состоит из блоков называемых модулями, и каждый отдельный файл по своей сути — отдельный блок, чья область видимости изолирована от других таких же блоков.

Теперь, перед тем как мы узнаем, как сделать эти штуки видимыми вне модуля, рассмотрим более подробно как загружаются модули в Node.js. Сейчас речь пойдёт о том месте, где пишется require. require используют для загрузки модуля, обычно присваивая результат его работы какой-то переменной:

var misc = require('./misc');

Конечно же, до тех пор, пока наш модуль ничего не отдаёт, все приведённые примеры бесполезны. А чтобы наш модуль что-нибудь отдал, мы будем использовать module.exports:

var x = 5;
var addX = function(value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

Вот теперь наш модуль стал гораздо более полезным:

var misc = require('./misc');
console.log("Добавив %d к 10 мы получим %d", misc.x, misc.addX(10));

Есть ещё такой способ отдать штуки из нашего модуля:

var User = function(name, email) {
  this.name = name;
  this.email = email;
};
module.exports = User;

Разница между этими подходами не велика, но важна. Как видно, в данном случае мы экспортируем функцию напрямую:

module.exports.User = User;
//vs
module.exports = User;

Всё это к тому, что потом её будет легче использовать:

var user = require('./user');

var u = new user.User();
//vs
var u = new user();

Выгода от использования такого способа заключается в том, что в данном случае нам не важно, будет ли ваш модуль представлять контейнер c экспортируемыми значениями или нет.

Чтобы ещё более красочно представить процесс взаимодействия модулей, давайте рассмотрим следующий пример:

var powerLevel = function(level) {
  return level > 9000 ? "it's over 9000!!!" : level;
};
module.exports = powerLevel;

Когда вы подключите данный модуль используя require, фактически им будет являться функция, что позволит вам сделать следующее:

require('./powerlevel')(9050);

Что, по сути, является упрощённой записью следующего кода:

var powerLevel = require('./powerlevel')
powerLevel(9050);

Надеюсь эти примеры помогут вам освоиться!
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 14
  • –1
    Если очень хочется, то есть GLOBAL. Всё что в нём есть видно во всех модулях.
    • 0
      Перевод не о том, чего «хочется» а о «как это работает».
      • 0
        Нода — это не что-то волшебное. Это обычный js. Модули исполняются по-умолчанию не в глобальном пространстве, а команда require возвращает содержимое переменной module.exports. Да это так, но фраза:
        Привычный доступ к переменной x и функции addX из другого файла невозможен.

        не верна.
        В одном файле делаем:
        (0,eval)('var x = 33;');
        

        В другом:
        console.log(x);
        

        Переменная отлично видна. Из этого следует, что ничто не мешает написать модуль, который будет подключать другой модуль, но так, что бы тот исполнялся в глобальном контексте и загрязнял своими переменными адресное пространство как в браузере.
        • 0
          Чем мне приглянулась оригинальная статья, так это своим простым языком. Здесь очень понятно описана основная механика require и exports. Согласен с тем, что автор статьи допустил неточность. Хотя, как мне кажется, многое в этой статье говорит о том, что она для новичков. Ну и неточность эта в данных обстоятельствах скорее сродни утверждению «на ноль делить нельзя».
    • 0
      Вот ещё хорошие ответы.
      var global = Function('return this')() || (42, eval)('this');
      

      Но делать это надо только если действительно знаешь чего хочешь, обычно модульной системы ноды достаточно, но если поставить целью разработать более чудесную модульную систему или разрабатывается библиотека, которая ведёт себя как жквери или андерскор, помещая некоторые переменные в глобальную область видимости, то это именно то решение.
      • 0
        а что за магическое 42?
        • 0
          Там может быть что угодно, главное — вызвать евал без контекста.
          • +8
            Прочитайте Дугласа Адамса.
        • 0
          Для полноты, добавлю немного важных деталей.

          require может принимать пути к модулям как с расширением, так и без расширения. Может быть загружен либо модуль ядра, либо конкретный файл, либо файл с таким же именем и расширением ".js", либо файл в такой директории с именем index.js, либо подобным образом один из файлов внутри mode_modules, либо директория внутри node_modules. К слову, это делает удобным декомпозицию и эволюционное превращение некоторых подключаемых файлов в библиотеки в аккуратных директориях или даже в npm. Подробнее об алгоритме загрузки можно узнать из документации:
          Порядок загрузки require
          require(X) from module at path Y
          1. If X is a core module,
          a. return the core module
          b. STOP
          2. If X begins with './' or '/' or '../'
          a. LOAD_AS_FILE(Y + X)
          b. LOAD_AS_DIRECTORY(Y + X)
          3. LOAD_NODE_MODULES(X, dirname(Y))
          4. THROW «not found»

          LOAD_AS_FILE(X)
          1. If X is a file, load X as JavaScript text. STOP
          2. If X.js is a file, load X.js as JavaScript text. STOP
          3. If X.node is a file, load X.node as binary addon. STOP

          LOAD_AS_DIRECTORY(X)
          1. If X/package.json is a file,
          a. Parse X/package.json, and look for «main» field.
          b. let M = X + (json main field)
          c. LOAD_AS_FILE(M)
          2. If X/index.js is a file, load X/index.js as JavaScript text. STOP
          3. If X/index.node is a file, load X/index.node as binary addon. STOP

          LOAD_NODE_MODULES(X, START)
          1. let DIRS=NODE_MODULES_PATHS(START)
          2. for each DIR in DIRS:
          a. LOAD_AS_FILE(DIR/X)
          b. LOAD_AS_DIRECTORY(DIR/X)

          NODE_MODULES_PATHS(START)
          1. let PARTS = path split(START)
          2. let ROOT = index of first instance of «node_modules» in PARTS, or 0
          3. let I = count of PARTS — 1
          4. let DIRS = []
          5. while I > ROOT,
          a. if PARTS[I] = «node_modules» CONTINUE
          c. DIR = path join(PARTS[0… I] + «node_modules»)
          b. DIRS = DIRS + DIR
          c. let I = I — 1
          6. return DIRS


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

          Тело модуля — это тело функции, вызываемой один раз при инициализации модуля. Внутри этой функции существует переменная module, которая ссылается на этот самый загружаемый модуль. У module есть поле module.exports, именно эта переменная возвращается в качестве значения вызова require(). Помимо module доступна также переменная exports, которая является синонимом module.exports, что позволяет писать укороченные конструкции вида «exports.User = User». Однако переопределение значения переменной exports закономерно отвязывает её от module.exports, поэтому при том, что конструкция «exports.User = User» работать будет, конструкция виде «exports = User» работать уже не будет, и для экспорта целого объекта в качестве модуля следует писать «module.exports = User». Грубо говоря, чтобы понять как это работает, проще всего при написании модуля думать, что где-то в невидимой высоте присутствуют такие строчки:
          var module = наш_модуль;
          module.exports = {};
          var exports = module.exports;
          
          • +2

            Привычный доступ к переменной x и функции addX из другого файла невозможен. Это никак не связано с использованием var.
            ..

            Это напрямую связано с var, попробуйте написать без var и переменная будет объявлена в глобальном контексте.

            пример:

            foo.js
            foo = 42;
            


            index.js
            console.log(foo); // => ReferenceError: foo is not defined
            
            require('./foo.js');
            console.log(foo); // => 42
            


            Почему так происходит? nodejs не делает магии, она просто оборачивает все модули в конструкцию вида:
            (function (exports, require, module, __filename, __dirname) { 
            
            /* код модуля */
            
            });
            

            Именно поэтому переменные объявленные через var видно только внутри модуля.

            Так же следует помнить о том, что require возвращает поле exports объекта module, поэтому если мы хотим вернуть свой объект мы должны переопределять это поле.
            • 0
              Часто, для того чтобы обьяснить как работают модули в node.js я использую вот такой пример который многое обьясняет.
              app.js

              var rnd = require('test.js');
              console.log(rnd);
              var rnd2 = require('test.js');
              console.log(rnd2);

              test.js

              var rand = Math.random();
              module.exports = rand;

              node app.js
              Который выдаст подряд в консоль два одинаковых числа.
              • 0
                Объясните пожалуйста, почему так получается?
                rnd = require('test.js'); мы возвращаем значение rand в котором лежит сгенерированное случайное число, вопрос — когда оно генерируется? во время первого присваивания rnd = require('test.js');? Почему во время второго присваивания берется то же значение? Потому что модуль уже подключен и exports получены?
                • 0
                  Почему во время второго присваивания берется то же значение?

                  Тело модуля — это тело функции, вызываемой один раз при инициализации модуля.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.