Книга: JavaScript. Подробное руководство, 6-е издание

9.9.2. Область видимости функции как частное пространство имен

9.9.2. Область видимости функции как частное пространство имен

Модули имеют экспортируемый ими общедоступный прикладной интерфейс (API): это функции, классы, свойства и методы, предназначенные для использования другими программистами. Однако зачастую для внутренних нужд модуля требуются дополнительные функции или методы, которые не предназначены для использования за пределами модуля. Примером может служить функция Set._v2s() из примера 9.6 - для нас было бы нежелательно, чтобы пользователи класса Set вызывали эту функцию, поэтому было бы неплохо сделать ее недоступной извне.

Этого можно добиться, определив модуль (в данном случае класс Set) внутри функции. Как описывалось в разделе 8.5, переменные и функции, объявленные внутри другой функции, являются локальными по отношению к этой функции и недоступны извне. Таким образом, область видимости функции (называемой иногда «функцией модуля») можно использовать как частное пространство имен модуля. Пример 9.24 демонстрирует, как это может выглядеть применительно к нашему классу Set.

Пример 9.24. Класс Set внутри функции модуля

// Объявляет глобальную переменную Set и присваивает ей значение, возвращаемое
// функцией. Круглые скобки, окружающие объявление функции, свидетельствуют о том,
// что функция будет вызвана сразу после ее объявления и что присваивается значение,
// возвращаемое функцией, а не сама функция. Обратите внимание, что это выражение
// определения функции, а не инструкция, поэтому наличие имени "invocation"
// не вызывает создание глобальной переменной,
var Set = (function invocation() {
  function Set() { // Эта функция-конструктор - локальная переменная,
    this.values = {}; // Свойство для хранения множества
    this.n = 0; // Количество значений в множестве
    this.add.apply(this, arguments); // Все аргументы являются значениями,
  } // добавляемыми в множество
  // Далее следуют определения методов в Set.prototype.
  // Для экономии места программный код опущен
  Set.prototype.contains = function(value) {
    // Обратите внимание, что v2s() вызывается без префикса Set._v2s()
    return this.values.hasOwnProperty(v2s(value));
  };
  Set.prototype.size = function() { return this.n; };
  Set.prototype.add = function() { /* ... */ };
  Set.prototype.remove = function() { /* ... */ };
  Set.prototype.foreach = function(f, context) {/*...*/>;
  // Далее следуют вспомогательные функции и переменные, используемые
  // методами выше. Они не являются частью общедоступного API модуля и скрыты
  // в области видимости функции, благодаря чему не требуется объявлять их как
  // свойства класса Set или использовать символ подчеркивания в качестве префикса.
  function v2s(val) { /* ... */ }
  function objectld(o) { /* ... */ }
  var nextId = 1;
  // Общедоступным API этого модуля является функция-конструктор Set().
  // Нам необходимо экспортировать эту функцию за пределы частного
  // пространства имен, чтобы ее можно было использовать за ее пределами.
  // В данном случае конструктор экспортируется за счет передачи его
  // в виде возвращаемого значения. Он становится присваиваемым значением
  // в выражении в первой строке выше, return Set;
}()); // Вызвать функцию сразу после ее объявления.

Обратите внимание, что такой прием вызова функции сразу после ее определения является характерным для языка JavaScript. Программный код, выполняемый в частном пространстве имен, предваряется текстом «(function() {» и завершается «}());». Открывающая круглая скобка в начале сообщает интерпретатору, что это выражение определения функции, а не инструкция, поэтому в префикс можно добавить любое имя функции, поясняющее ее назначение. В примере 9.24 было использовано имя «invocation», чтобы подчеркнуть, что функция вызывается сразу же после ее объявления. Точно так же можно было бы использовать имя «namespace», чтобы подчеркнуть, что функция играет роль пространства имен.

После того как модуль окажется заперт внутри функции, ему необходим некоторый способ экспортировать общедоступный API для использования за пределами функции модуля. В примере 9.24 функция модуля возвращает конструктор, который тут же присваивается глобальной переменной. Сам факт возврата значения из функции ясно говорит о том, что оно экспортируется за пределы области видимости функции. Модули, определяющие более одного элемента API, могут возвращать объект пространства имен. Для нашего модуля с классами множеств можно было бы написать такой программный код:

// Создает единственную глобальную переменную, хранящую все модули,
// имеющие отношение к коллекциям
var collections;
if (!collections) collections = {};
// Теперь определить модуль sets
collections.sets = (function namespace() {
  // Здесь находятся определения различных классов множеств,
  // использующих локальные переменные и функции
  // ... Большая часть программного кода опущена...
  // Экспортировать API в виде возвращаемого объекта пространства имен
  return {
    // Экспортируемое имя свойства : имя локальной переменной
    AbstractSet: AbstractSet,
    NotSet: NotSet,
    AbstractEnumerableSet: AbstractEnumerableSet,
    SingletonSet: SingletonSet,
    AbstractWritableSet: AbstractWritableSet,
    ArraySet: ArraySet
  };
}());

Можно предложить похожий прием, определив функцию модуля как конструктор, который будет вызываться с ключевым словом new и экспортировать значения за счет их присваивания:

var collections;
if (!collections) collections = {};
collections.sets = (new function namespace() {
  // ... Большая часть программного кода опущена ...
  // Экспортировать API в объекте this
  this.AbstractSet = AbstractSet;
  this.NotSet = NotSet; // И так далее...
  // Обратите внимание на отсутствие возвращаемого значения.
}());

Если объект глобального пространства имен уже определен, функция модуля может просто присваивать значения свойствам этого объекта и вообще ничего не возвращать:

var collections;
if (!collections) collections = {};
collections.sets = {};
(function namespace() {
  // ... Большая часть программного кода опущена ...
  // Экспортировать общедоступный API в объект пространства имен, созданный выше
  collections.sets.AbstractSet = AbstractSet;
  collections.sets.NotSet = NotSet; // И так далее...
  // Инструкция return не требуется, потому что экспортирование выполняется выше.
}());

Фреймворки, реализующие инструменты загрузки модулей, могут предусматривать собственные методы экспортирования API модулей. Внутри модуля может определяться функция provides(), которая выполняет регистрацию его API, или объект exports, в котором модуль должен сохранять свой API. Пока в языке JavaScript отсутствуют инструменты управления модулями, вам придется использовать средства создания и экспортирования модулей, которые лучше подходят для используемой вами библиотеки инструментов.

Оглавление книги


Генерация: 1.585. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз