Концепция AMD. Понятие модуля
Glossary Item Box
Общие положения
Начиная с версии 7.0, клиентская часть приложения bpm'online имеет модульную структуру, то есть реализована в виде набора блоков функциональности, каждый из которых реализован в отдельном модуле. В процессе работы приложения загрузка модулей и их зависимостей выполняется в соответствии с подходом Asynchronous Module Definition (AMD).
По сути, подход AMD декларирует механизм определения и асинхронной загрузки модулей и их зависимостей, который позволяет в процессе работы с системой подгружать только те данные, которые необходимы для работы в текущий момент. Концепцию AMD поддерживают различные JavaScript-фреймворки. В bpm'online для работы с модулями используется загрузчик RequireJS.
Понятие модуля
Понятие модуль можно сформулировать как фрагмент кода, инкапсулированный в обособленный блок, который может быть загружен и выполнен самостоятельно.
Создание модулей в специфике JavaScript декларируется паттерном программирования "Модуль". Классическим приемом реализации этого паттерна является использование анонимных функций, возвращающих определенное значение (объект, функцию и т.д.), которое ассоциируется с модулем. При этом значение модуля экспортируется в глобальный объект. Например:
// Немедленно вызываемое функциональное выражение (IIFE). Aнонимная функция, // которая инициализирует свойство myGlobalModule глобального объекта функцией, // возвращающей значение модуля. Таким образом, фактически происходит загрузка модуля, // к которому в дальнейшем можно обращаться через глобальное свойство myGlobalModule. (function () { // Обращение к некоторому модулю, от которого зависит текущий модуль. // Этот модуль уже (на момент обращения к нему) // должен быть загружен в глобальную переменную SomeModuleDependency. // Контекcт this в данном случае — глобальный объект. var moduleDependency = this.SomeModuleDependency; // Объявление в свойстве глобального объекта функции, возвращающей значение модуля. this.myGlobalModule = function () { return someModuleValue; }; }());
Интерпретатор, обнаруживая в коде такое функциональное выражение, сразу вычисляет его. В результате выполнения в свойство myGlobalModule глобального объекта будет помещена функция, которая будет возвращать само значение модуля.
Основные недостатки такого подхода заключаются в сложности декларирования и использования модулей-зависимостей для таких модулей:
- В момент выполнения анонимной функции все зависимости модуля должны быть уже загружены ранее.
- Загрузка модулей-зависимостей выполняется через HTML-элемент <script><script/> в заголовке страницы, и обращение к ним в дальнейшем осуществляется через имена глобальных переменных. При этом разработчик должен четко представлять и реализовывать порядок загрузки всех модулей-зависимостей.
- Как следствие предыдущего пункта — модули загружаются до начала рендеринга страницы, поэтому в модулях нельзя обращаться к элементам управления страницы для реализации какой-либо пользовательской логики.
Таким образом, это, по сути, означает отсутствие возможностей динамической загрузки модулей, применения какой-либо дополнительной логики при загрузке модулей и т.д. В крупных проектах, каким является bpm'online, возникает дополнительная проблема в виде сложности управления большим количеством модулей со многими зависимостями, которые могут перекрывать друг друга.
Загрузчик RequireJS
Загрузчик RequireJS предоставляет механизм объявления и загрузки модулей, базирующийся на концепции AMD, и позволяющий избежать перечисленных выше недостатков. Основные принципы работы механизма загрузчика RequireJS:
- Объявление модуля выполняется в специальной функции define(), которая регистририрует функцию-фабрику для инстанцирования модуля, но при этом не загружает его немедленно в момент вызова.
- Зависимости модуля передаются как массив строковых значений, а не через свойства глобального объекта.
- Загрузчик выполняет загрузку всех модулей-зависимостей, переданных в качестве аргументов в define(). Модули загружаются асинхронно, при этом фактически порядок их загрузки определяется загрузчиком произвольно.
- После того как загрузчиком будут загружены все указанные зависимости модуля, будет вызвана функция-фабрика, которая вернет значение модуля. При этом в функцию-фабрику в качестве агрументов будут переданы загруженные модули-зависимости.
Объявление модуля. Функция define()
Для того чтобы загрузчик мог работать с асинхронным модулем, этот модуль должен быть объявлен в исходном коде с помощью функции define() следующим образом.
define(
ModuleName,
[dependencies],
function (dependencies) {
}
);
Параметры функции define() приведены в таблице 1.
Табл. 1. — Параметры функции define()
Аргумент | Значение |
---|---|
ModuleName |
Строка с именем модуля. Необязательный параметр. Если его не указать, загрузчик самостоятельно присвоит модулю имя в зависимости от его расположения в дереве скриптов приложения. Однако для того чтобы иметь возможность обратиться к модулю из других частей приложения (в том числе для асинхронной загрузки такого модуля как зависимости другого модуля), имя модуля должно быть однозначно определено. |
dependencies |
Массив имен модулей, от которых зависит данный модуль. Необязательный параметр. RequireJS выполняет загрузку всех зависимостей, переданных в массиве. Нужно учитывать, что порядок перечисления зависимостей в массиве dependencies должен соответствовать порядку перечисления параметров, передаваемых в функцию-фабрику. Функция-фабрика будет вызвана только после того, как будут загружены все зависимости, перечисленные в dependencies. Загрузка модулей-зависимостей выполняется асинхронно. |
function(dependencies) |
Анонимная функция-фабрика, которая инстанцирует сам модуль. Обязательный параметр. В качестве аргументов в функцию передаются объекты, которые ассоциируются загрузчиком с модулями-зависимостями, перечисленными в аргументе dependencies. Через эти аргументы осуществляется доступ к свойствам и методам модулей-зависимостей внутри создаваемого модуля. Порядок перечисления модулей в dependencies должен соответствовать порядку агрументов функции-фабрики. Функция-фабрика будет вызвана только после того, как будут загружены все модули-зависимости данного модуля (перечисленные в параметре dependencies). Фабрика должна возвращать значение, которое загрузчик будет ассоциировать как экспортируемое значение создаваемого модуля. В качестве возвращаемого значения могут выступать:
|
Пример использования функции define() для объявления модуля SumModule, реализующего функциональность суммирования двух чисел.
// Модуль с именем SumModule не имеет зависимостей. // Поэтому в качестве второго аргумента передается пустой массив, а // анонимной функции-фабрике не передаются никакие параметры. define("SumModule", [], function () { // Тело анонимной функции содержит в себе внутреннюю реализацию функциональности модуля. var calculate = function (a, b) { return a + b; }; // Возвращаемое функцией значение — объект, которым является модуль для системы. return { // Описание объекта. В данном случае модуль представляет собой объект со свойством summ. // Значение этого свойства — функция с двумя аргументами, которая возвращает сумму этих аргументов. summ: calculate }; });
Функция-фабрика возвращает в качестве значения модуля объект, которым модуль будет являться для системы.