Как структурировать программы Javascript в сложных веб-приложениях? - PullRequest
27 голосов
/ 09 марта 2010

У меня есть проблема, которую нелегко описать. Я пишу веб-приложение, которое активно использует вызовы jQuery и AJAX. Сейчас у меня нет большого опыта работы с Javascript archicture, но я понимаю, что моя программа не имеет хорошей структуры. Я думаю, что у меня слишком много идентификаторов, ссылающихся на одну и ту же (по крайней мере, более или менее) вещь.

Давайте посмотрим на произвольный примерный виджет пользовательского интерфейса, который составляет крошечную часть приложения: виджет может быть частью окна, а окно может быть частью оконного менеджера:

  1. Обработчики событий используют элементы DOM в качестве параметров. Элемент DOM представляет виджет в браузере.
  2. Много раз я использую объекты jQuery (в основном обертки вокруг элементов DOM), чтобы что-то делать с виджетом. Иногда они используются временно, иногда они сохраняются в переменной для более поздних целей.
  3. Вызовы функций AJAX используют строковые идентификаторы для этих виджетов. Они обрабатываются на стороне сервера.
  4. Кроме того, у меня есть класс виджетов, экземпляры которого представляют виджет. Он создается через новый оператор.

Теперь у меня есть как-то четыре разных идентификатора объекта для одной и той же вещи, которые нужно синхронизировать до тех пор, пока страница не будет загружена заново. Это, кажется, не очень хорошая вещь.

Любой совет?



EDIT:
@ Will Morgan : это дизайнер форм, который позволяет создавать веб-формы в браузере. Бэкэнд - Zope, сервер веб-приложений на Python. Трудно получить более ясное объяснение, так как это общая проблема, которую я наблюдаю все время, когда делаю Javasscript-разработку с трио jQuery, DOM-деревом и моими собственными экземплярами класса-прототипа.

EDIT2:
Я думаю, что было бы полезно сделать пример, хотя и искусственный. Ниже вы видите виджет журнала, который можно использовать для добавления элемента блока на веб-страницу, на которой отображаются зарегистрированные элементы.

makeLogger = function(){
     var rootEl = document.createElement('div');
     rootEl.innerHTML = 'Logged items:';
     rootEl.setAttribute('class', 'logger');

     var append = function(msg){
           // append msg as a child of root element.
           var msgEl = document.createElement('div');
           msgEl.innerHTML = msg;
           rootEl.appendChild(msgEl);
     };

     return {
          getRootEl: function() {return rootEl;},
          log      : function(msg) {append(msg);}
     };
};

// Usage 
var logger = makeLogger();
var foo = document.getElementById('foo');
foo.appendChild(logger.getRootEl());
logger.log('What\'s up?');

На данный момент у меня есть обертка вокруг HTMLDivElement (размещенный объект). Имея экземпляр logger (собственный объект) под рукой, я могу легко работать с ним с помощью функции logger.getRootEl () .
Я застреваю, когда у меня есть только элемент DOM, и мне нужно что-то делать с открытым API, возвращаемым функцией makeLogger (например, в обработчиках событий). И вот тут начинается беспорядок. Мне нужно держать все нативные объекты в хранилище или что-то еще, чтобы я мог получить снова. Было бы намного приятнее иметь соединение (например, свойство объекта) с размещенного объекта обратно к моему нативному объекту. Я знаю, что это можно сделать, но у него есть некоторые недостатки:

  • Этот вид (циклических) ссылок может привести к утечке памяти до IE7
  • Когда передавать размещенный объект и когда передавать собственный объект (в функциях)?

Сейчас я делаю обратную ссылку с помощью метода jQuery data (). Но в целом мне не нравится, как я должен отслеживать отношения между размещенным объектом и его нативным аналогом.

Как вы справляетесь с этим сценарием?



EDIT3:
После некоторого понимания я получил от примера Anurag ..
@ Anurag: Если я правильно понял ваш пример, критический момент - установить правильное (хотя правильное зависит от ваших потребностей) контекст выполнения для обработчиков событий. И это в вашем случае экземпляр объекта презентации, который выполняется с помощью функции bind () Mootool. Таким образом, вы гарантируете, что вы ВСЕГДА имеете дело с объектом-оболочкой (я назвал его нативным объектом) вместо объекта DOM, верно?

Примечание для читателя: Вы не обязаны использовать Mootools для достижения этой цели. В jQuery вы бы настраивали свои обработчики событий с помощью функции $. Proxy () , или, если вы используете простой старый Javascript, вы бы использовали свойство apply , которое предоставляет каждая функция ,

Ответы [ 4 ]

5 голосов
/ 09 марта 2010

Вы можете использовать глобальный реестр:

window.WidgetRegistry = {};
window.WidgetRegistry['foowidget'] = new Widget('#myID');

и когда AJAX-вызовы возвращаются, они могут получить виджет следующим образом:

var widgetID = data.widgetID;
if (widgetID in window.WidgetRegistry) {
    var widget = window.WidgetRegistry[widgetID];
}

Для ваших вызовов jQuery: я предполагаю, что они относительно недороги, поскольку jQuery кэширует объекты для последующего использования. Но вы могли бы расширить предложенное выше WidgetRegistry, используя .data():

var $widget = $('#myWidget');
var widgetID = 'foo';
$widget.data('widget', widgetID);

Таким образом, вы можете сохранить идентификатор виджета, прикрепленный к каждому объекту jQuery, и повторно получить к нему доступ из глобального реестра.

Тестирование, если у объекта jQuery есть существующий виджет:

return $('#test').data('widget') &&
       ($('#test').data('widget') in window.WidgetRegistry);

Обратите внимание, что это всего лишь предложения. На самом деле, существует множество способов добиться такой консолидации. Если вы хотите объединить свой код глубже с jQuery, вы можете расширить объект jQuery , чтобы вы могли написать что-то вроде:

$('#widget').widget({'foo':'bar'});
// and/or
var allWidgets = $('*:widget');
// ...
2 голосов
/ 13 марта 2010

Для четырех объектов, которые необходимо синхронизировать, вы можете иметь один объект и передавать ссылку в конструкторе или в качестве аргументов функции.

Способ решения этой проблемы - никогда не терять ссылку на объект-оболочку. Всякий раз, когда требуется объект DOM (например, вставка на страницу), этот объект-оболочка предоставляет его. Но, прикрепив этот виджет на экран, объект-обертка устанавливает всю обработку событий и код обработки AJAX, специфичные для виджета, поэтому ссылка на обертку всегда поддерживается в этих обработчиках событий и обратных вызовах AJAX.

Я создал простой пример для jsfiddle с использованием MooTools, который может иметь для вас смысл.

1 голос
/ 12 марта 2010

Я не уверен, что полностью понял ваш вопрос, но я попытаюсь высказать некоторые идеи.

На мой взгляд, вы должны создать базовый класс виджетов, который содержит общие функции для виджетов.

Давайте используем, например, AppName.Widgets.base (). Одной из переменных экземпляра является _events, который является объектом, который хранит события как ключи и функционирует как значения. Таким образом, каждый класс определяет события для этого виджета, и вы можете легко связать их в конструкторе. Что касается строковых идентификаторов, самый простой способ - использовать toString ().

Пример:

namespace('AppName.Widgets'); // you can find implementations easy

AppName.Widgets.base = function() {
    if (!this._type) return;

    this._dom = $('div.widget.'+this._type);
    for (var e in this._events) {
        this._dom.bind(e, this._events[e]);
    }

    this.toString = function() { return this._type; };
}

AppName.Widgets.example = function() { // extends AppName.Widgets.base
    this._type   = 'example';
    this._events = { 'click' : function(e) { alert('click'); }  };

    AppName.Widgets.base.call(this);
}
0 голосов
/ 16 марта 2010

Многое из того, что вы можете или не можете сделать, будет зависеть от того, насколько вы контролируете JavaScript. Лично мне часто приходится использовать библиотеки, созданные другими, чтобы я мог получить только узел DOM для работы, но вместо этого мне действительно нужен мой объект. В этих случаях я считаю, что использование функции данных в jQuery очень удобно. Используя функцию данных, вы можете «сохранить» свой объект внутри узла DOM, чтобы получить его позже.

Учитывая приведенный выше пример, вот как вы можете использовать функцию данных для возврата вашего виджета после использования функций, которые используют только узел DOM.

makeLogger = function(){
     var rootEl = document.createElement('div');
     rootEl.innerHTML = 'Logged items:';
     rootEl.setAttribute('class', 'logger');

     var append = function(msg){
           // append msg as a child of root element.
           var msgEl = document.createElement('div');
           msgEl.innerHTML = msg;
           rootEl.appendChild(msgEl);
     };

     var self = {
          getRootEl: function() {return rootEl;},
          log      : function(msg) {append(msg);}
     };

    // Save a copy to the domNode
    $(rootEl).data("logger", self);
    return self;
};

// Example of only getting the dom node
function whatsUp (domNode){
   // Get the logger from the domNode
   $(domNode).data('logger').log('What\'s up?');
}

// Usage
var logger = makeLogger();
var loggerNode = logger.getRootEl();
var foo = document.getElementById('foo');
foo.appendChild(loggerNode);
whatsUp(loggerNode);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...