Паттерны для масштабируемых JavaScript-приложений ч.4
- Details
- 06 Август 2014 13:55
Паттерн «Модуль»
«Модуль» — это популярная реализация паттерна, инкапсулирующего приватную информацию, состояние и структуру, используя замыкания. Это позволяет оборачивать публичные и приватные методы и переменные в модули, и предотвращать их попадание в глобальный контекст, где они могут конфликтовать с интерфейсами других разработчиков. Паттерн «модуль» возвращает только публичную часть API, оставляя всё остальное доступным только внутри замыканий.
Это хорошее решение для того, чтобы скрыть внутреннюю логику от посторонних глаз и производить всю тяжелую работу исключительно через интерфейс, который вы определите для использования в других частях вашего приложения. Этот паттерн очень похож на немедленно-вызываемые функции (IIFE), за тем исключением, что модуль вместо функции, возвращает объект.
Важно заметить, что в JavaScript нет настоящей приватности. В отличии от некоторых традиционных языков, он не имеет модификаторов доступа. Переменные технически не могут быть объявлены как публичные или приватные, и нам приходится использовать область видимости для того, чтобы эмулировать эту концепцию. Благодаря замыканию, объявленные внутри модуля переменные и методы доступны только изнутри этого модуля. Переменные и методы, объявленные внутри объекта, возвращаемого модулем, будут доступны всем.
Ниже вы можете увидеть корзину покупок, реализованную с помощью паттерна «модуль». Получившийся компонент находится в глобальном объекте basketModule
, и содержит всё, что ему необходимо. Находящийся внутри него, массив basket
приватный, и другие части вашего приложения не могут напрямую взаимодействовать с ним. Массив basket существует внутри замыкания, созданного модулем, и взаимодействовать с ним могут только методы, находящиеся в том же контексте (например, addItem()
, getItem()
).
var basketModule = (function() { var basket = []; // приватная переменная return { // методы доступные извне addItem: function(values) { basket.push(values); }, getItemCount: function() { return basket.length; }, getTotal: function() { var q = this.getItemCount(),p=0; while(q--){ p+= basket[q].price; } return p; } } }());
Внутри модуля, как вы заметили, мы возвращаем объект. Этот объект автоматически присваивается переменной basketModule
, так что с ним можно взаимодействовать следующим образом:
// basketModule - это объект со свойствами, которые могут также быть и методами: basketModule.addItem({item:'bread', price:0.5}); basketModule.addItem({item:'butter', price:0.3}); console.log(basketModule.getItemCount()); console.log(basketModule.getTotal()); // А следующий ниже код работать не будет: console.log(basketModule.basket); // undefined потому что не входит в возвращаемый объект console.log(basket); // массив доступен только из замыкания
Методы выше, фактически, помещены в неймспейс basketModule
.
Исторически, паттерн «модуль» был разработан в 2003 году группой людей, в число которых входил Ричард Корнфорд. Позднее, этот паттерн был популяризован Дугласом Крокфордом в его лекциях, и открыт заново в блоге YUI благодаря Эрику Мирагилиа.
Давайте посмотрим на реализацию «модуля» в различных библиотеках и фреймворках.
Dojo
Dojo старается обеспечивать поведение похожее на классы с помощью dojo.declare
, который, кроме создания «модулей», также используется и для других вещей. Давайте попробуем, для примера, определить basket
как модуль внутри неймспейса store
:
// традиционный способ var store = window.store || {}; store.basket = store.basket || {}; // с помощью dojo.setObject dojo.setObject("store.basket.object", (function() { var basket = []; function privateMethod() { console.log(basket); } return { publicMethod: function() { privateMethod(); } }; }()));
Лучшего результата можно добиться, используя dojo.provide
и миксины.
YUI
Следующий код, по большей части, основан на примере реализации паттерна «модуль» в фреймворке YUI, разработанным Эриком Миргалиа, но более самодокументирован.
YAHOO.store.basket = function () { // приватная переменная: var myPrivateVar = "Ко мне можно получить доступ только из YAHOO.store.basket."; // приватный метод: var myPrivateMethod = function() { YAHOO.log("Я доступен только при вызове из YAHOO.store.basket"); } return { myPublicProperty: "Я - публичное свойство", myPublicMethod: function() { YAHOO.log("Я - публичный метод"); // Будучи внутри корзины я могу получить доступ к приватным переменный и методам: YAHOO.log(myPrivateVar); YAHOO.log(myPrivateMethod()); // Родной контекст метода myPublicMethod сохранён // поэтому мы имеет доступ к this YAHOO.log(this.myPublicProperty); } }; }();
jQuery
Существует множество способов, чтобы представить jQuery-код в виде паттерна «модуль», даже если этот код не напоминает привычные jQuery-плагины. Бен Черри ранее предлагал способ, при котором, если у модулей есть общие черты, то они объявляются через функцию-обертку.
В следующем примере функция library
используется для объявления новой библиотеки и, автоматически, при создании библиотеки (т.е. модуля), связывает вызов метода init
с document.ready
.
function library(module) { $(function() { if (module.init) { module.init(); } }); return module; } var myLibrary = library(function() { return { init: function() { /* код модуля */ } }; }());
Бен Черри — Погружение в паттерн «Модуль»
Джон Ханн — Будущее — это модули, а не фреймворки
Натан Смит — Ссылки на window и document в модулях (gist)
Литеральная нотация объектов
В литеральной нотации объект описывается внутри блока фигурных скобок ({}
), как набор разделенных запятой пар ключ/значение. Ключи объекта могут быть как строками, так и идентификаторами. После имени ставится двоеточие. В объекте не должно стоять запятой после последней пары ключ/значение, так как это может привести к ошибкам.
Литерал объекта не требует использования оператора new
для создания экземпляра, но он не должен стоять в начале выражения, так как открытая {
может быть воспринята как начало блока. Ниже вы можете увидеть пример модуля, определенного с помощью литеральной нотации объекта. Новые члены объекта могут быть добавлены с помощью конструкции myModule.property = 'someValue'
;
var myModule = { myProperty: 'someValue', // Литералы объектов могут содержать свойства и методы. // ниже в свойстве определен другой объект, // для описания конфигурации: myConfig: { useCaching: true, language: 'en' }, // Очень простой метод myMethod: function() { console.log('I can haz functionality?'); }, // вывод значения заданного в конфигурации myMethod2: function() { console.log('Caching is: ' + ((this.myConfig.useCaching) ? 'enabled' : 'disabled')); }, // переопределение конфигурации myMethod3: function(newConfig) { if (typeof newConfig == 'object') { this.myConfig = newConfig; console.log(this.myConfig.language); } } }; myModule.myMethod(); // 'I can haz functionality' myModule.myMethod2(); // Вывод 'enabled' myModule.myMethod3({language:'fr',useCaching:false}); // 'fr'
Ребекка Мёрфи — Использование объектов для организации вашего кода
Стоян Стефанов — 3 способа определения класса в JavaScript
Бен Алман — Разъяснения по литералам объектов (понятия JSON-объект не существует)
Джон Резиг - Простое наследование в JavaScript
- Паттерны для масштабируемых JavaScript-приложений ч.1
- Паттерны для масштабируемых JavaScript-приложений ч.2
- Паттерны для масштабируемых JavaScript-приложений ч.3
- Паттерны для масштабируемых JavaScript-приложений ч.5
- Паттерны для масштабируемых JavaScript-приложений ч.6
- Паттерны для масштабируемых JavaScript-приложений ч.7
- Tags
- Author
- Андрей Рачков
Авторизуйтесь, чтобы оставить комментарий