Прошло несколько недель (хорошо, несколько месяцев) с момента моего последнего сообщения в блоге о шаблонах проектирования 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, все будет просто работать!