// → 2
Способ лучше – использовать конструктор Function
. Он принимает два аргумента – строку, содержащую список имён аргументов через запятую, и строку, содержащую тело функции.
var plusOne = new Function("n", "return n + 1;");
console.log(plusOne(4));
// → 5
Это то, что нам надо. Мы обернём код модуля в функцию, и её область видимости станет областью видимости нашего модуля.
Require
Вот минимальная версия функции 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
находится внутри функции, которая вызывается многократно, будет потеряно время и энергия.
Это можно решить, храня уже загруженные модули в объекте, и возвращая существующее значение, когда он грузится несколько раз.
Вторая проблема – модуль не может экспортировать переменную напрямую, только через объект export
. К примеру, модулю может потребоваться экспортировать только конструктор объекта, объявленного в нём. Сейчас это невозможно, поскольку require
всегда использует объект exports
в качестве возвращаемого значения.
Традиционное решение – предоставить модули с другой переменной, module
, которая является объектом со свойством exports
. Оно изначально указывает на пустой объект, созданный require
, но может быть перезаписано другим значением, чтобы экспортировать что-либо ещё.
function require(name) {
if (name in require.cache)
return require.cache[name];
var code = new Function("exports, module", readFile(name));
var exports = {}, module = {exports: exports};
code(exports, module);
require.cache[name] = module.exports;
return module.exports;
}
require.cache = Object.create(null);
Сейчас у нас есть система модулей, использующих одну глобальную переменную require
, чтобы позволять модулям искать и использовать друг друга без выхода в глобальную область видимости.
Такой стиль системы модулей называется CommonJS
, по имени псевдостандарта, который первым его описал. Он встроен в систему Node.js. Настоящие реализации делают гораздо больше описанного мною. Главное, что у них есть более умный способ перехода от имени модуля к его коду, который разрешает загружать модули по относительному пути к файлу, или же по имени модуля, указывающему на локально установленные модули.
Медленная загрузка модулей
Хотя и возможно использовать стиль CommonJS для браузера, но он не очень подходит для этого. Загрузка файла из Сети происходит медленнее, чем с жёсткого диска. Пока скрипт в браузере работает, на сайте ничего другого не происходит (по причинам, которые станут ясны к 14 главе). Значит, если бы каждый вызов require
скачивал что-то с дальнего веб-сервера, страница бы зависла на очень долгое время при загрузке.
Можно обойти это, запуская программу типа Browserify с вашим кодом перед выкладыванием её в веб. Она просмотрит все вызовы require
, обработает все зависимости и соберёт нужный код в один большой файл. Веб-сайт просто грузит этот файл и получает все необходимые модули.
Второй вариант – оборачивать код модуля в функцию, чтобы загрузчик модулей сначала грузил зависимости в фоне, а потом вызывал функцию, инициализирующую модуль, после загрузки зависимостей. Этим занимается система AMD (асинхронное определение модулей).