- PVSM.RU - https://www.pvsm.ru -
Прочитав книгу «Паттерны для масштабируемых JavaScript-приложений» [1], решил реализовать данную структуру на практике.
Подробности описания модульной структуры изложены по вышеуказанной ссылке, но если вы еще не знакомы с этим материалом, то вкратце: масштабируемые JavaScript-приложения строятся на трёх китах, а точнее — на трёх паттернах: Медиатор, Фасад, Модуль.
Основные критерии в реализации:
Итак, опираясь на вышеперечисленное, решил написать небольшой код, который, надеюсь, поможет некоторым разработчикам лучше понять, что именно представляет из себя модульная архитектура и как она работает.

Функцию ядра берет на себя паттерн Медиатор (он же Посредник). Использование этого шаблона исключает прямые взаимодействия между независимыми объектами за счет введения объекта-посредника. Когда какой-либо из модулей изменяет свое состояние, он извещает об этом ядро приложения, а то в свою очередь сообщает об изменениях всем остальным модулям, которые должны знать об этом.
Что собой будет представлять ядро (Медиатор/Посредник) в модульной архитектуре?
Представим, что у нас есть два очень простых модуля, которые мы хотим добавить в свой проект: один получает два параметра в виде цифр, после чего возвращает их сумму, а второй получает параметр и выводит его на экран.
Прежде я хочу сказать пару слов о паттерне Фасад. Как я писал раньше: модуль — это самовызывающаяся функция, переменные и методы которой скрыты от пользователя, кроме тех переменных и тех методов, которые мы сами хотим сделать публичными. Эти публичные данные и называется фасадом, за которым находится архитектура модуля.
Давайте рассмотрим это на примере. Пусть переменная Namespace будет нашим пространством имен, в котором будет объект modules, в нем и будут хранится модули.
Namespace.modules = {};
Добавим функцию sum в Namespace.modules. Обратите внимание на комментарии к коду.
'use strict';
Namespace.modules.sum = (function () {
function getSum(x, y) {
return x + y;
}
/**
Это и есть паттерн Фасад.
Как видите, функция getSum() - является приватной и у пользователя нет доступа к ней,
но благодаря return мы возвращаем новый объект и записываем ссылку на приватную функцию getSum() в свойство getSum.
Теперь приватная функция будет доступна по ссылке возвращаемого метода getSum.
*/
return {
getSum: getSum // не обязательно давать публичным и приватным данным одни и те же названия
}
})();
Проделаем туже операцию со-вторым модулем.
'use strict';
Namespace.modules.createText= (function () {
function addText(txt) {
var paragraph = document.createElement('p');
paragraph.innerHTML = txt;
document.body.appendChild(paragraph);
}
return {
addText: addText
}
})();
Как видите, ничего сложного: один модуль складывает два числа, второй — выводит данные на экран. Я старался упростить все как можно больше, так как сейчас главное разобраться в самой архитектуре модульных приложений и понять, как она должна работать.
Модули готовы, теперь подготовим ядро, которое будет импортировать эти модули и работать с ними.
Как вы помните, Namespace.modues — это статическая переменная, которая хранит в себе модули, другим словом — это пространство имен.
Раз у нас уже есть одна глобальная переменная Namespace, то на ней и остановимся. Запишем в нее функцию, которая будет принимать два параметра. Первый — массив с модулями, а второй — функция обратного вызова, которая эти самые модули и будет использовать по своему назначению.
var sut = Namespace;
it("Проверим, что модуль sum и createText подключены и готовы к использованию в функции обратного вызова", function () {
sut(['sum', 'createText'], function (Modules) {
var sum = Modules.sum,
createText = Modules.createText;
expect(sum.getSum).toBeDefined(); // true
expect(createText.addText).toBeDefined(); // true
expect(sum.getMultiplication).toBeUndefined(); // false. А таких методов у нас нет.
expect(createText.deleteText).toBeUndefined(); // false. И этого метода нету
})
});
Из ядра приложения мы не должны изменять наши модули, функция Namespace будет возвращать только копии.
it("При удалении импортированных модулей, должны удалятся копии, но не оригиналы.", function () {
sut(['sum', 'createText'], function (Modules) {
var sum = Modules.sum,
createText = Modules.createText;
expect(sum.getSum).toBeDefined(); // true
expect(createText.addText).toBeDefined(); // true
/** удаляем методы импортируемых модулей */
delete sum.getSum;
delete createText.addText;
expect(sum.getSum).toBeUndefined(); // true
expect(createText.addText).toBeUndefined(); // true
/** так как мы работаем с копиями, а не с оригиналами, то модули работаю корректно */
expect(sut.modules.sum.getSum).toBeDefined(); // true
expect(sut.modules.sum.getSum(10, 10)).toBe(20); // true
expect(sut.modules.createText.addText).toBeDefined(); // true
});
});
Итак, когда мы понимаем, что именно мы хотим от функции Namespace , то давайте её реализуем.
'use strict';
(function () {
var Namespace = function () {
var args = [].slice.call(arguments),
/** Cохраняем в пеерменную последний параметр переданный ф-ии Namespace.
* Этот параметр - ф-я обратного вызова, в которояй мы будем работать с модулями */
callback = args.pop(),
/** modules будет хранить массив с модулями, которы мы будем импортирвоать в ядро */
modules = (args[0] && typeof args[0] === 'string') ? args : args[0],
/** В этот объект мы будем копировать модули и дальше работать с ним в ф-ии обратного вызова */
copyModules = {},
i;
/** Теперь мы можем вызывать Namespace не только, как экземпляр объекта,
* но и как функцию */
if (!(this instanceof Namespace)) {
return new Namespace(modules, callback);
}
/** Создаем копии модулей */
function addModuleToCopyObject(module) {
copyModules[module] = {};
function copyModule(parent, child) {
for (i in parent) {
if (parent.hasOwnProperty(i)) {
child[i] = parent[i]
}
}
return child;
}
copyModule(Namespace.modules[module], copyModules[module]);
}
modules.forEach(addModuleToCopyObject);
/** Пеердаем объект с копиями модулей в функцию обратного вызова */
callback(copyModules);
};
/** Если объекта Namespace нету в глобальном объекте, то мы его добавляем */
if (typeof window.Namespace === 'undefined') {
window.Namespace = Namespace;
}
})();
/** В modules будут хранится все наши модули */
Namespace.modules = {};
А вот и ядро нашего приложения, он же паттерн Медиатор, он же паттерн Посредник.
'use strict';
/** @param{array} - первый параметр - массив с импортируемыми модулями
* @param{function} - второй параметр - функция обратного вызова, в которой в качестве параметра Modules передается объект с импортируемыми копиями модулей.
*/
Namespace(['sum', 'createText'], function (Modules) {
/** Модули ничего не знаю друг о друге, они импортируются в ядро приложения, где и используются вместе */
var moduleSum = Modules['sum'],
moduleCreateText = Modules['createText'];
var a = 1000,
b = 1000;
moduleCreateText.addText(moduleSum.getSum(a, b));
});
Чего не хватает функции Namespace?
<script src="javascript/namespace-v0.0.3.js"></script>
<script src="javascript/modules/module1.js"></script>
<script src="javascript/modules/module2.js"></script>
<script src="javascript/mediator.js"></script>
А если у нас сотня таких модулей? В идеале будет, если мы загрузим только файл с Namespace, а все остальные модули будут грузиться тогда, когда будем передавать их в ядро приложения или грузить по требованию. То есть, если приложение действительно нуждается в этом самом модуле (кликнули на чат — загрузился модуль чата).
Все вышеизложенное было написано с целью показать и объяснить новичкам, как работает модульная структура в JavaScript. Сейчас есть много хороших библиотек, которые уже умеют работать с модулями (тот же require.js). Да и через пару тройку лет можно будет во всю использовать EcmaScript 6, где уже внедрена модульная структура.
Оригинальная статья Эдди Османи: «Patterns For Large-Scale JavaScript Application Architecture» [2].
Та же статья на русском: «Паттерны для масштабируемых JavaScript-приложений» [1].
JavaScript Patterns [3] — есть что почитать про модули и пространство имен, да и вообще советую тем, кто по каким-то причинам не знаком с этой книгой.
Спасибо за внимание. Буду признателен за комментарии по улучшению статьи.
Автор: sir_Galahad
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/85996
Ссылки в тексте:
[1] «Паттерны для масштабируемых JavaScript-приложений»: http://largescalejs.ru/
[2] «Patterns For Large-Scale JavaScript Application Architecture»: http://addyosmani.com/largescalejavascript/
[3] JavaScript Patterns: http://www.amazon.com/dp/0596806752/?tag=w3clubs-20
[4] Источник: http://habrahabr.ru/post/253258/
Нажмите здесь для печати.