繁体   English   中英

如何在复杂的 Web 应用程序中构建 Javascript 程序?

[英]How to structure Javascript programs in complex web applications?

我有一个问题,不容易描述。 我正在编写一个 Web 应用程序,它充分利用了 jQuery 和 AJAX 调用。 现在我在 Javascript 架构方面没有很多经验,但我意识到我的程序没有一个好的结构。 我想我有太多的标识符指代相同的(至少或多或少)事物。

让我们看一下构成应用程序一小部分的任意示例 UI 小部件:小部件可能是窗口的一部分,窗口可能是窗口管理器的一部分:

  1. 事件处理程序使用 DOM 元素作为参数。 DOM 元素代表浏览器中的小部件。
  2. 很多时候我使用 jQuery 对象(基本上是围绕 DOM 元素的包装器)来处理小部件。 有时它们被暂时使用,有时它们被存储在一个变量中供以后使用。
  3. AJAX 函数调用使用这些小部件的字符串标识符。 它们在服务器端处理。
  4. 除此之外,我有一个小部件类,它的实例代表一个小部件。 它是通过 new 运算符实例化的。

现在我有四个不同的对象标识符用于同一件事,需要保持同步,直到重新加载页面。 这似乎不是一件好事。

有什么建议吗?



编辑:
*@Will Morgan*:它是一个表单设计器,允许在浏览器中创建 Web 表单。 后端是 Zope,一个 Python Web 应用程序服务器。 很难说得更清楚,因为这是我在使用 jQuery、DOM 树和我自己的原型类实例进行 Javascript 开发时一直观察到的一个普遍问题。

编辑2:
我认为举个例子会很有帮助,尽管是人为的。 下面您会看到一个记录器小部件,可用于将块元素添加到显示已记录项目的网页。

 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.getRootEl()轻松地使用它。
当我手头只有 DOM 元素并且需要对函数makeLogger返回的公共 API 执行某些操作时(例如在事件处理程序中),我会卡住。 这就是混乱开始的地方。 我需要将所有本机对象保存在存储库或其他东西中,以便我可以再次检索。 拥有从托管对象返回到我的本机对象的连接(例如对象属性)会更好。 我知道它可以做到,但它有一些缺点:

  • 这些(循环)引用可能会导致 IE7 内存泄漏
  • and when to pass the (in functions)?何时传递以及何时传递本(在函数中)?

现在,我使用 jQuery 的 data() 方法进行反向引用。 但总而言之,我不喜欢我必须跟踪托管对象与其本机对应对象之间关系的方式。

你如何处理这种情况?



编辑3:
在我从 Anurag 的例子中获得了一些见解之后..
for the event handlers. *@Anurag:* 如果我理解你的例子是正确的,关键点是为事件处理程序设置正确的(正确的取决于你的需要) 在您的情况下,这是使用 Mootool 的 bind() 函数完成的演示对象实例。 所以你确保你*总是*处理包装器对象(我称之为本机对象)而不是 DOM 对象,对吗?

You're not forced to use Mootools to achieve this. 您不必使用 Mootools 来实现这一点。 在 jQuery 中,您将使用 *$.proxy()* 函数设置您的事件处理程序,或者如果您使用普通的旧 Javascript,您将使用每个函数公开的 *apply* 属性。

您可以使用全局注册表:

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 缓存对象供以后使用。 但是您可以使用.data()扩展上述建议的WidgetRegistry

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

通过这种方式,您可以存储附加到每个 jQuery 对象的小部件 ID,并从全局注册表重新访问它。

测试,如果一个 jQuery 对象有一个现有的小部件:

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

请注意,这些只是建议。 实际上,有很多方法可以实现这样的整合。 如果您想将您的代码与 jQuery 更深入地结合,您可以扩展 jQuery object ,以便您可以编写如下内容:

$('#widget').widget({'foo':'bar'});
// and/or
var allWidgets = $('*:widget');
// ...

对于需要同步的四个对象,您可以拥有一个对象并在构造函数中传递引用,或作为函数参数传递。

我解决这个问题的方法是永远不会丢失对包装器对象的引用。 每当需要一个 DOM 对象时(例如插入到页面中),这个包装器对象就会提供它。 但是将该小部件粘贴到屏幕上,包装器对象设置特定于小部件的所有事件处理和 AJAX 处理代码,因此在这些事件处理程序和 AJAX 回调中始终维护包装器的引用。

我已经使用 MooTools 在 jsfiddle 上创建了一个简单的示例,这可能对您有意义。

我不确定我是否完全理解你的问题,但我会尝试指出一些想法。

在我看来,您应该创建基本小部件类,其中包含小部件的通用功能。

让我们以 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);
}

您能做或不能做的很多事情取决于您对 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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM