Нам понадобятся две вещи. Во-первых, функция >readFile
, возвращающая содержимое файла в виде строки. В стандартном JavaScript такой функции нет, но разные окружения, такие как браузер или Node.js, предоставляют свои способы доступа к файлам. Пока притворимся, что у нас есть такая функция. Во-вторых, нам нужна возможность выполнить содержимое этой строки как код.
Есть несколько способов получить данные (строку кода) и выполнить их как часть текущей программы.
Самый очевидный – оператор >eval
, который выполняет строку кода в текущем окружении. Это плохая идея – он нарушает некоторые свойства окружения, которые обычно у него есть, например изоляция от внешнего мира.
>function evalAndReturnX(code) {
> eval(code);
> return x;
>}
>console.log(evalAndReturnX("var x = 2"));
>// → 2
Способ лучше – использовать конструктор >Function
. Он принимает два аргумента – строку, содержащую список имён аргументов через запятую, и строку, содержащую тело функции.
>var plusOne = new Function("n", "return n + 1;");
>console.log(plusOne(4));
>// → 5
Это то, что нам надо. Мы обернём код модуля в функцию, и её область видимости станет областью видимости нашего модуля.
Вот минимальная версия функции >require
:
>function require(name) {
> var code = new Function("exports", readFile(name));
> var exports = {};
> code(exports);
> return exports;
>}
>console.log(require("weekDay").name(1));
>// → Вторник
Так как конструктор >new Function
оборачивает код модуля в функцию, нам не надо писать функцию, оборачивающую пространство имён, внутри самого модуля. А так как >exports
является аргументом функции модуля, модулю не нужно его объявлять. Это убирает много мусора из нашего модуля-примера.
>var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
>exports.name = function(number) {
> return names[number];
>};
>exports.number = function(name) {
> return names.indexOf(name);
>};
При использовании такого шаблона модуль обычно начинается с объявления нескольких переменных, которые загружают модули, от которых он зависит.
>var weekDay = require("weekDay");
>var today = require("today");
>console.log(weekDay.name(today.dayNumber()));
У такого простого варианта >require
есть недостатки. Во-первых, он загрузит и выполнит модуль каждый раз, когда его грузят через >require
– если у нескольких модулей есть одинаковые зависимости, или вызов >require
находится внутри функции, которая вызывается многократно, будет потеряно время и энергия.
Это можно решить, храня уже загруженные модули в объекте, и возвращая существующее значение, когда он грузится несколько раз.