Традиционное решение – предоставить модули с другой переменной, 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 (асинхронное определение модулей).
Наша простая программа с зависимостями выглядела бы в AMD так:
define(["weekDay", "today"], function(weekDay, today) {
console.log(weekDay.name(today.dayNumber()));
});
Функция define
здесь самая важная. Она принимает массив имён модулей, а затем функцию, принимающую один аргумент для каждой из зависимостей. Она загрузит зависимости (если они не были загружены ранее) в фоне, позволяя странице работать, пока файлы скачиваются. Когда всё загружено, define
вызывает данную ему функцию, с интерфейсами этих зависимостей в качестве аргументов.
Загруженные таким образом модули должны содержать вызовы define
. В качестве их интерфейса используется то, что было возвращено функцией, переданной в define
. Вот модуль weekDay
:
define([], function() {
var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
});
Чтобы показать минимальную реализацию define
, притворимся, что у нас есть функция backgroundReadFile
, которая принимает имя файла и функцию, и вызывает эту функцию с содержимым этого файла, как только он будет загружен. (В главе 17 будет объяснено, как написать такую функцию).
Чтоб отслеживать модули, пока они загружаются, define
использует объекты, описывающие состояние модулей, сообщает нам, доступны ли они уже, и предоставляет их интерфейс по доступности.
Функция getModule
принимает имя и возвращает такой объект, и убеждается в том, что модуль поставлен в очередь загрузки. Она использует кеширующий объект, чтобы не грузить один модуль дважды.
var defineCache = Object.create(null);
var currentMod = null;
function getModule(name) {
if (name in defineCache)
return defineCache[name];
var module = {exports: null,
loaded: false,
onLoad: []};
defineCache[name] = module;
backgroundReadFile(name, function(code) {