【问题标题】:How to structure Javascript programs in complex web applications?如何在复杂的 Web 应用程序中构建 Javascript 程序?
【发布时间】:2011-01-25 08:35:55
【问题描述】:

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

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

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

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

有什么建议吗?



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

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

  • 这类(循环)引用可能会导致内存泄漏到 IE7
  • 何时传递托管对象以及何时传递本机对象(在函数中)?

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

您如何处理这种情况?



EDIT3:
在我从 Anurag 的示例中获得了一些见解之后..
*@Anurag:* 如果我正确理解了您的示例,那么关键是设置正确的(不过,正确的取决于您的需要)执行上下文事件处理程序。在您的情况下,这是演示对象实例,它是使用 Mootool 的 bind() 函数完成的。所以你要确保你*总是*处理包装对象(我称之为本机对象)而不是 DOM 对象,对吧?

给读者的提示:您不必使用 Mootools 来实现这一点。在 jQuery 中,您可以使用 *$.proxy()* 函数设置事件处理程序,或者如果您使用普通的旧 Javascript,您将使用每个函数公开的 *apply* 属性。

【问题讨论】:

  • 它是一个什么样的网络应用程序?有任何示例以便我们提供帮助吗?
  • 所以用更简单的语言来说,“应该如何构造代码和数据来组合/推断/执行对一组不断变化的 UI 元素事件的适当响应 - 并应对集合中内容的动态变化元素 - 出于 IE 内存管理的原因,元素需要由其他对象和函数通过键/名称间接引用(而不是对象之间潜在的循环直接引用) - 并且元素具有多个键/名称;并且元素也既有额外的自定义包装类,也有 jQuery 包装类”?
  • @martinr:你在跟我开玩笑,不是吗?如此妖孽的气息,怎能再平淡无奇?也许是因为我不是母语人士:)
  • LOL 好吧,更短,更简洁。
  • 这是正确的!图书馆无所谓。您可以使用任何库或 POJO(普通的 Javascript 对象)来做同样的事情。但是像你说的重要的事情是将execution context设置为本地对象(或与本地对象有朋友的任何其他地方)。

标签: javascript architecture web-applications


【解决方案1】:

您能做什么或不能做什么取决于您对 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);

【讨论】:

  • 我认为 OP 目前正在使用数据 API 将对象关联到 DOM 节点。
  • 是的,当我说:“现在,我使用 jQuery 的 data() 方法进行反向引用。但总而言之,我不喜欢我必须保持的方式跟踪托管对象与其本地对象之间的关系。”但尽管如此,我觉得它并不优雅。
【解决方案2】:

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

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

我使用 MooTools 在 jsfiddle 上创建了一个 simple example,这对您来说可能有意义。

【讨论】:

  • 我认为这非常接近我的核心问题。我会更彻底地调查你的例子。感谢您的努力!
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    您可以使用全局注册表:

    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 结合,你可以extend the jQuery object,这样你就可以编写如下内容:

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

    【讨论】:

      猜你喜欢
      • 2016-07-12
      • 2013-04-17
      • 1970-01-01
      • 1970-01-01
      • 2011-10-17
      • 1970-01-01
      • 2011-04-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多