return names.indexOf(name);

  };

})(this.weekDay = {});

console.log(weekDay.name(weekDay.number("Четверг")));

// → Четверг

<p>Отсоединяемся от глобальной области видимости</p>

Такой шаблон часто используется в модулях JavaScript, предназначающихся для браузера. Модуль возьмёт одну глобальную переменную и обернёт свой код в функцию, чтобы у него было своё личное пространство имён. Но с этим шаблоном бывают проблемы, когда много модулей требуют одно и то же имя, или когда вам надо загрузить две версии модуля одновременно.

Подкрутив кое-что, мы можем сделать систему, разрешающую одному модулю обращаться к интерфейсному объекту другого, без выхода в глобальную ОВ. Наша цель – функция require, которая, получая имя модуля, загрузит его файл (с диска или из сети, в зависимости от платформы) и вернёт соответствующее значение с интерфейсом.

Этот подход решает проблемы, упомянутые ранее, и у него есть ещё одно преимущество – зависимости вашей программы становятся явными, и поэтому сложнее случайно вызвать ненужный вам модуль без чёткого его объявления.

Нам понадобятся две вещи. Во-первых, функция readFile, возвращающая содержимое файла в виде строки. В стандартном JavaScript такой функции нет, но разные окружения, такие как браузер или Node.js, предоставляют свои способы доступа к файлам. Пока притворимся, что у нас есть такая функция. Во-вторых, нам нужна возможность выполнить содержимое этой строки как код.

<p>Выполняем данные как код</p>

Есть несколько способов получить данные (строку кода) и выполнить их как часть текущей программы.

Самый очевидный – оператор 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

Это то, что нам надо. Мы обернём код модуля в функцию, и её область видимости станет областью видимости нашего модуля.

<p>Require</p>

Вот минимальная версия функции 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 в качестве возвращаемого значения.

Перейти на страницу:

Похожие книги