Выбрать главу

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

(function() {

  function square(x) { return x * x; }

  var hundred = 100;

  console.log(square(hundred));

})();

// → 10000

Этот код выводит квадрат сотни, но в реальности это мог бы быть модуль, добавляющий метод к какому-то прототипу, или настраивающий виджет на веб-странице. Он обёрнут в функцию для предотвращения загрязнения глобальной ОВ используемыми им переменными.

А зачем мы заключили функцию в круглые скобки? Это связано с глюком синтаксиса JavaScript. Если выражение начинается с ключевого слова function, это функциональное выражение. А если инструкция начинается с function, это объявление функции, которое требует названия, и, так как это не выражение, не может быть вызвано при помощи скобок () после неё. Можно представлять себе заключение в скобки как трюк, чтобы функция принудительно интерпретировалась как выражение.

Объекты в качестве интерфейсов

Представьте, что нам надо добавить ещё одну функцию в наш модуль «день недели». Мы уже не можем возвращать функцию, а должны завернуть две функции в объект.

var weekDay = function() {

  var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];

  return {

    name: function(number) { return names[number]; },

    number: function(name) { return names.indexOf(name); }

  };

}();

console.log(weekDay.name(weekDay.number("Воскресенье")));

// → Воскресенье

Когда модуль большой, собирать все возвращаемые значения в объект в конце функции неудобно, потому что многие возвращаемые функции будут большими, и вам было бы удобнее их записывать где-то в другом месте, рядом со связанным с ними кодом. Удобно объявить объект (обычно называемый exports) и добавлять к нему свойства каждый раз, когда нам надо что-то экспортировать. В следующем примере функция модуля принимает объект интерфейса как аргумент, позволяя коду снаружи функции создать его и сохранить в переменной. Снаружи функции this ссылается на объект глобальной области видимости.

(function(exports) {

  var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];

  exports.name = function(number) {

    return names[number];

  };

  exports.number = function(name) {

    return names.indexOf(name);

  };

})(this.weekDay = {});

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

// → Четверг

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

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

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

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

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

Выполняем данные как код

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

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

function evalAndReturnX(code) {

  eval(code);

  return x;

}

console.log(evalAndReturnX("var x = 2"));