简体   繁体   English

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

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

I have a problem, which is not easily described.我有一个问题,不容易描述。 I'm writing a web application that makes strong usage of jQuery and AJAX calls.我正在编写一个 Web 应用程序,它充分利用了 jQuery 和 AJAX 调用。 Now I don't have a lot of experience in Javascript archicture, but I realize that my program has not a good structure.现在我在 Javascript 架构方面没有很多经验,但我意识到我的程序没有一个好的结构。 I think I have too many identifiers referring to the same (at least more or less) thing.我想我有太多的标识符指代相同的(至少或多或少)事物。

Let's have an look at an arbitrary exemplary UI widget that makes up a tiny part of the application: The widget may be a part of a window and the window may be a part of a window manager:让我们看一下构成应用程序一小部分的任意示例 UI 小部件:小部件可能是窗口的一部分,窗口可能是窗口管理器的一部分:

  1. The eventhandlers use DOM elements as parameters.事件处理程序使用 DOM 元素作为参数。 The DOM element represents a widget in the browser. DOM 元素代表浏览器中的小部件。
  2. A lot of times I use jQuery objects (Basically wrappers around DOM elements) to do something with the widget.很多时候我使用 jQuery 对象(基本上是围绕 DOM 元素的包装器)来处理小部件。 Sometimes they are used transiently, sometimes they are stored in a variable for later purposes.有时它们被暂时使用,有时它们被存储在一个变量中供以后使用。
  3. The AJAX function calls use string identifiers for these widgets. AJAX 函数调用使用这些小部件的字符串标识符。 They are processed server side.它们在服务器端处理。
  4. Beside that I have a widget class whose instances represent a widget.除此之外,我有一个小部件类,它的实例代表一个小部件。 It is instantiated through the new operator.它是通过 new 运算符实例化的。

Now I have somehow four different object identifiers for the same thing, which needs to be kept in sync until the page is loaded anew.现在我有四个不同的对象标识符用于同一件事,需要保持同步,直到重新加载页面。 This seems not to be a good thing.这似乎不是一件好事。

Any advice?有什么建议吗?



EDIT: 编辑:
*@Will Morgan*: It's a form designer that allows to create web forms within the browser. *@Will Morgan*:它是一个表单设计器,允许在浏览器中创建 Web 表单。 The backend is Zope, a python web application server. 后端是 Zope,一个 Python Web 应用程序服务器。 It's difficult to get more explicit as this is a general problem I observe all the time when doing Javascript development with the trio jQuery, DOM tree and my own prototyped class instances. 很难说得更清楚,因为这是我在使用 jQuery、DOM 树和我自己的原型类实例进行 Javascript 开发时一直观察到的一个普遍问题。

EDIT2:编辑2:
I think it would helpful to make an example, albeit an artificial one.我认为举个例子会很有帮助,尽管是人为的。 Below you see a logger widget that can be used to add a block element to a web page in which logged items are displayed.下面您会看到一个记录器小部件,可用于将块元素添加到显示已记录项目的网页。

 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?');

At this point I have a wrapper around the HTMLDivElement (the hosted object).在这一点上,我有一个围绕 HTMLDivElement(托管对象)的包装器。 With having the logger instance (the native object) at hand I can easily work with it through the function logger.getRootEl() .有了记录器实例(本机对象),我可以通过函数logger.getRootEl()轻松地使用它。
Where I get stuck is when I only have the DOM element at hand and need to do something with the public API returned by function makeLogger (eg in event handlers).当我手头只有 DOM 元素并且需要对函数makeLogger返回的公共 API 执行某些操作时(例如在事件处理程序中),我会卡住。 And this is where the mess starts.这就是混乱开始的地方。 I need to hold all the native objects in a repository or something so that I can retrieve again.我需要将所有本机对象保存在存储库或其他东西中,以便我可以再次检索。 It would be so much nicer to have a connection (eg a object property) from the hosted object back to my native object.拥有从托管对象返回到我的本机对象的连接(例如对象属性)会更好。 I know it can be done, but it has some drawbacks:我知道它可以做到,但它有一些缺点:

  • These kind of (circular) references are potentially memory leaking up to IE7这些(循环)引用可能会导致 IE7 内存泄漏
  • When to pass the hosted object and when to pass the native object (in functions)?何时传递托管对象以及何时传递本机对象(在函数中)?

For now, I do the back referencing with jQuery's data() method.现在,我使用 jQuery 的 data() 方法进行反向引用。 But all in all I don't like the way I have to keep track of the relation between the hosted object and its native counterpart.但总而言之,我不喜欢我必须跟踪托管对象与其本机对应对象之间关系的方式。

How do you handle this scenario?你如何处理这种情况?



EDIT3: 编辑3:
After some insight I've gained from Anurag's example.. 在我从 Anurag 的例子中获得了一些见解之后..
*@Anurag:* If I've understood your example right, the critical point is to set up the correct (what's correct depends on your needs, though) execution context for the event handlers. *@Anurag:* 如果我理解你的例子是正确的,关键点是为事件处理程序设置正确的(正确的取决于你的需要)执行上下文 And this is in your case the presentation object instance, which is done with Mootool's bind() function. 在您的情况下,这是使用 Mootool 的 bind() 函数完成的演示对象实例。 So you ensure that you're *ALWAYS* dealing with the wrapper object (I've called it the native object) instead of the DOM object, right? 所以你确保你*总是*处理包装器对象(我称之为本机对象)而不是 DOM 对象,对吗?

A note for the reader: You're not forced to use Mootools to achieve this. 给读者的注意事项:您不必使用 Mootools 来实现这一点。 In jQuery, you would setup your event handlers with the *$.proxy()* function, or if you're using plain old Javascript, you would utilize the *apply* property that every function exposes. 在 jQuery 中,您将使用 *$.proxy()* 函数设置您的事件处理程序,或者如果您使用普通的旧 Javascript,您将使用每个函数公开的 *apply* 属性。

You could use a global registry:您可以使用全局注册表:

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

and when AJAX calls return, they can get the widget like this:当 AJAX 调用返回时,他们可以像这样获得小部件:

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

For your jQuery calls: I'd guess they are relatively inexpensive, since jQuery caches objects for later use.对于您的 jQuery 调用:我猜它们相对便宜,因为 jQuery 缓存对象供以后使用。 But you could extend the above suggested WidgetRegistry by using .data() :但是您可以使用.data()扩展上述建议的WidgetRegistry

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

In this way, you can store the widget ID attached to each jQuery object and re-access it from the global registry.通过这种方式,您可以存储附加到每个 jQuery 对象的小部件 ID,并从全局注册表重新访问它。

Testing, if an jQuery object has an existing widget:测试,如果一个 jQuery 对象有一个现有的小部件:

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

Note, that these are just suggestions.请注意,这些只是建议。 Actually, there are dozens of ways to achieve a consolidation like this.实际上,有很多方法可以实现这样的整合。 If you want to combine your code deeper with jQuery, you could extend the jQuery object , so that you could write something like:如果您想将您的代码与 jQuery 更深入地结合,您可以扩展 jQuery object ,以便您可以编写如下内容:

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

For the four objects that need to be synchronized, you could have a single object and pass the reference around in a constructor, or as function arguments.对于需要同步的四个对象,您可以拥有一个对象并在构造函数中传递引用,或作为函数参数传递。

The way I fix this problem is to never lose a reference to the wrapper object.我解决这个问题的方法是永远不会丢失对包装器对象的引用。 Whenever a DOM object is needed (for example inserting into the page), this wrapper object provides it.每当需要一个 DOM 对象时(例如插入到页面中),这个包装器对象就会提供它。 But sticking that widget onto the screen, the wrapper object sets up all event handling and AJAX handling code specific to the widget, so the reference the the wrapper is maintained at all times in these event handlers and AJAX callbacks.但是将该小部件粘贴到屏幕上,包装器对象设置特定于小部件的所有事件处理和 AJAX 处理代码,因此在这些事件处理程序和 AJAX 回调中始终维护包装器的引用。

I've created a simple example on jsfiddle using MooTools that might make sense to you.我已经使用 MooTools 在 jsfiddle 上创建了一个简单的示例,这可能对您有意义。

I'm not sure I've fully understood your question, but I'll try to point some ideas.我不确定我是否完全理解你的问题,但我会尝试指出一些想法。

In my opinion, you should make base widget class, which contains common functionality for widgets.在我看来,您应该创建基本小部件类,其中包含小部件的通用功能。

Let's use for example AppName.Widgets.base().让我们以 AppName.Widgets.base() 为例。 One of the instance variables is _events, which is object that stores events as keys and function as values.实例变量之一是 _events,它是将事件存储为键并将函数存储为值的对象。 That way each class defines the events for this widget, and you can easily bind them in the constructor.这样每个类都定义了这个小部件的事件,你可以很容易地在构造函数中绑定它们。 As for the string identifiers, the easiest way is to use toString().至于字符串标识符,最简单的方法是使用 toString()。

Example:例子:

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);
}

A lot of what you can or can't do will depend on how much control you have over the javascript.您能做或不能做的很多事情取决于您对 javascript 的控制程度。 Personally I often have to use libraries built by others so I might only get a DOM node to work with, but I really need my object instead.就我个人而言,我经常不得不使用其他人构建的库,因此我可能只能使用 DOM 节点,但我确实需要我的对象。 In these cases I find using the data feature in jQuery is very handy.在这些情况下,我发现使用 jQuery 中的数据功能非常方便。 By using the data feature, you can 'store' your object inside the DOM node to retrieve it later.通过使用数据功能,您可以将您的对象“存储”在 DOM 节点内,以便以后检索。

Given your example above, here's how you could use the data feature to get back your widget after having functions that only use the DOM node.鉴于您上面的示例,以下是您在拥有仅使用 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