Прошло несколько недель (хорошо, несколько месяцев) с момента моего последнего сообщения в блоге о шаблонах проектирования JavaScript. Я извиняюсь, но, честно говоря, вероятно, пройдет еще несколько недель, пока я снова не напишу в блоге на эту тему, так что, надеюсь, люди здесь не ожидают быстрой серии (ухмылка). Напомним, что идея этой серии заключается в создании реальных, практических примеров различных шаблонов проектирования JavaScript на основе книги «Изучение шаблонов проектирования JavaScript» Адди Османи. (См. Мой обзор здесь .) В этой записи блога я буду обсуждать шаблон модуля выявления.
Адди Османи описывает шаблон «Модуль раскрытия» как:
Шаблон «Модуль раскрытия» появился, когда [Кристиан] Хейлманн был разочарован тем, что ему пришлось повторять имя основного объекта, когда он хотел вызвать один открытый метод из другого или получить доступ к открытым переменным. Он также не любил требование шаблона модуля о необходимости переключаться на буквальную запись объекта для вещей, которые он хотел обнародовать.
Здесь есть два основных вопроса. Во-первых, концепция «повторять имя основного объекта, когда он хотел вызвать один публичный метод из другого …» Если ваш единственный опыт работы с шаблоном Module основан на моем предыдущем посте в блоге , то это может не иметь смысла. Давайте рассмотрим простой пример.
var revealModuleTest = (function() {
function priv1() {
return 1;
}
return {
doPriv:function() {
return priv1();
},
pub1:function() {
return 2;
},
testpub:function() {
return pub1()*2;
}
}
}());
//works just fine
console.log(revealModuleTest.doPriv());
//so will this
console.log(revealModuleTest.pub1());
//fail!
console.log(revealModuleTest.testpub());
У меня есть простой модуль, который называется showModuleTest. (И да, это не «пример из реальной жизни», но я хотел сначала продемонстрировать проблему простым блоком.) Мой модуль имеет один закрытый метод и три открытых метода. Первый, doPriv, просто оборачивает вызов приватного метода. Второй публичный метод, pub1, просто возвращает число 2. Наконец, testpub просто использует pub1 и умножает его на два.
Обратите внимание на три теста внизу. Как следует из комментариев, последний метод завершится ошибкой: Uncaught ReferenceError: pub1 не определен
Зачем? Когда результат модуля (все в этом возвращении) возвращается обратно к вызывающей стороне, область видоизменения изменяется таким образом, что сам по себе pub1 больше не адресуется из testpub. У вас не возникнет этой проблемы, если закрытый метод вызовет другой, но вы обязательно столкнетесь с этими методами, возвращенными вашим модулем. (Я не думаю, что я проделал большую работу, объясняя это — я могу вернуться и конкретизировать это.)
Исправление довольно тривиально — просто используйте тот же API, который вы использовали бы в своем коде, вызывающем модуль:
var revealModuleTest = (function() {
function priv1() {
return 1;
}
return {
doPriv:function() {
return priv1();
},
pub1:function() {
return 2;
},
testpub:function() {
return revealModuleTest.pub1()*2;
}
}
}());
//works just fine
console.log(revealModuleTest.doPriv());
//so will this
console.log(revealModuleTest.pub1());
//works now!
console.log(revealModuleTest.testpub());
Второе, о чем говорит Хайльманн, — это переключение на использование обозначений объектов для определения методов. Все в блоке возврата использует обозначение объекта. Лично это не кажется большой проблемой, но, учитывая все обстоятельства, я бы предпочел написать больше кода в синтаксисе функции x () по сравнению с x: function ().
Если это звучит так, будто модуль «Раскрытие» — это просто косметическое изменение, я думаю, это было бы справедливо. Но не просто отмахивайтесь от этого. Все, что делает вас более быстрым, эффективным и т. Д. В вашем развитии, вероятно, хорошо. Я также утверждаю, что проблема с «публичным публичным вызовом» — это то, с чем вы можете случайно столкнуться. Если шаблон «Модуль раскрытия» позволяет избежать этого, то это еще одна причина для его рассмотрения.
Давайте посмотрим на пример этого — снова — используя мой довольно глупый пример.
var revealModuleTest = (function() {
function priv1() {
return 1;
}
function pub1() {
return 2;
}
function testpub() {
return pub1() * 2;
}
return {
doPriv:priv1,
pub1:pub1,
testpub:testpub
}
}());
//works just fine
console.log(revealModuleTest.doPriv());
//so will this
console.log(revealModuleTest.pub1());
//not a fail!
console.log(revealModuleTest.testpub());
Как видите, это не радикально измененная версия. Он имеет такой же запах, но просто структурирован немного по-другому. Обратите внимание, что вся настоящая логика находится в частных функциях. Блок возврата теперь намного проще — он просто предоставляет публичный API.
Хорошо, а как насчет реального примера? В своем предыдущем посте в блоге я создал дневниковое приложение, которое позволяло вам создавать и читать дневниковые записи, хранящиеся в WebSQL. Я определил свой дневник как модуль. Вы можете просмотреть этот источник здесь .
Чтобы изменить это в соответствии с шаблоном Revealing Module, я переместил все в приватные методы и создал гораздо более простой блок возврата.
var diaryModule = (function() {
var db;
function initDB(t) {
t.executeSql('create table if not exists diary(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, body TEXT, image TEXT, published DATE)');
}
function dbErrorHandler(e) {
console.log('DB Error');
console.dir(e);
}
//Utility to convert record sets into array of obs
function fixResults(res) {
var result = [];
for(var i=0, len=res.rows.length; i<len; i++) {
var row = res.rows.item(i);
result.push(row);
}
return result;
}
//I'm a lot like fixResults, but I'm only used in the context of expecting one row, so I return an ob, not an array
function fixResult(res) {
if(res.rows.length) {
return res.rows.item(0);
} else return {};
}
function setup(callback) {
db = window.openDatabase("diary", 1, "diary", 1000000);
db.transaction(initDB, dbErrorHandler, callback);
}
function getEntries(start,callback) {
console.log('Running getEntries');
if(arguments.length === 1) callback = arguments[0];
db.transaction(
function(t) {
t.executeSql('select id, title, body, image, published from diary order by published desc',[],
function(t,results) {
callback(fixResults(results));
},dbErrorHandler);
}, dbErrorHandler);
}
function getEntry(id, callback) {
db.transaction(
function(t) {
t.executeSql('select id, title, body, image, published from diary where id = ?', [id],
function(t, results) {
callback(fixResult(results));
}, dbErrorHandler);
}, dbErrorHandler);
}
function saveEntry(data, callback) {
db.transaction(
function(t) {
t.executeSql('insert into diary(title,body,published) values(?,?,?)', [data.title, data.body, new Date().getTime()],
function() {
callback();
}, dbErrorHandler);
}, dbErrorHandler);
}
return {
setup:setup,
getEntries:getEntries,
getEntry:getEntry,
saveEntry:saveEntry
}
}());
Так что ты думаешь? Я должен быть честным. Когда я впервые прочитал об этом паттерне, я действительно не задумывался об этом. Как я уже сказал, это казалось просто косметическим. Но после просмотра изменений в моем дневнике, он просто … чувствует себя лучше.
Я не воспринимал это как «живую» демонстрацию, поскольку функциональность такая же, как и раньше. (И как напоминание, он работает только в браузерах WebKit, что плохо, и будет рассмотрено позже.) Если вы действительно хотите попробовать это, вы можете загрузить код из последней записи и вставить новый дневник. Поскольку он имеет точно такой же API, все будет просто работать!