Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax In Action (2006).pdf
Скачиваний:
63
Добавлен:
17.08.2013
Размер:
8.36 Mб
Скачать

306CHAPTER 8

Performance

1How much memory might he consume in terms of references to otherwise deleted objects? We know that our simple fido can remember only one Person at a time, but even so, that Person might have a reference to 500 otherwise-unreachable pet cats, so the extra memory consumption might be arbitrarily large.

2How long will the extra memory be held? In our simple script here, the answer is “not very long,” but we might later add extra steps in between deleting jim and deleting fido. Further, JavaScript tends toward eventdriven programming, and so, if the deletion of jim and of fido takes place in separate event handlers, we can’t predict a hard answer, not even a probabilistic one without performing some sort of use-case analysis.

Neither question is quite as easy to answer as it might seem. The best that we can do is to keep these sorts of questions in mind as we write and modify our code and to conduct tests to see if we’re right in our assumptions. We need to think about the usage patterns of our application while we code, not solely as an afterthought.

This covers the general principles of memory management. There are specific issues to be aware of in an Ajax application, so let’s address them next.

8.3.2Special considerations for Ajax

So far, we’ve covered some ground that is common to the memory management of most programming languages. Properly understanding concepts such as footprint and reachability are important when developing Ajax applications, but there are also issues that are specific to Ajax. With Ajax, we are operating in a managed environment, in a container that has exposed some of its native functionality and locked us out of others. This changes the picture somewhat.

In chapter 4, our Ajax application was divided into three notional subsystems: the Model, View, and Controller. The Model is usually composed of pure JavaScript objects that we have defined and instantiated ourselves. The View is composed largely of DOM nodes, which are native objects exposed to the JavaScript environment by the browser. The Controller glues the two together. It is in this layer that we need to pay special attention to memory management.

Breaking cyclic references

In section 4.3.1, we introduced a commonly used pattern for event handling, in which we attach domain model objects (that is, parts of the Model subsystem) to DOM nodes (that is, part of the View). Let’s recap on the example

JavaScript memory footprint

307

 

 

that we presented. Here is a constructor for a domain model object representing a pushbutton:

function Button(value,domEl){ this.domEl=domEl; this.value=value; this.domEl.buttonObj=this;

this.domEl.onclick=this.clickHandler;

}

Note that a two-way reference between the DOM element domEl and the domain object itself is created. Below, the event-handler function referenced in the constructor:

Button.prototype.clickHandler=function(event){ var buttonObj=this.buttonObj;

var value=(buttonObj && buttonObj.value) ? buttonObj.value : "unknown value";

alert(value);

}

Remember that the event-handler function will be called with the DOM node, not the Button object, as its context. We need a reference from the View to the Model in order to interact with the Model tier. In this case, we read its value property. In other cases where we have used this pattern in this book, we have invoked functions on the domain objects.

The domain model object of type Button will be reachable as long as any other reachable object has a reference to it. Similarly, the DOM element will remain reachable as long as any other reachable element refers to it. In the case of DOM elements, an element is always reachable if it is attached to the main document tree, even if no programmatic references are held to it. Thus, unless we explicitly break the link between the DOM element and the Button object, the Button can’t be garbage-collected as long as the DOM element is still part of the document.

When scripted domain model objects interact with the Document Object Model, it is possible to create a local JavaScript object that remains reachable via the DOM rather than through any global variables we have defined. To ensure that objects aren’t kept from garbage collection unnecessarily by we can write simple clean-up functions (a step back toward C++ object destructors in many ways, although we need to invoke them manually). For the Button object, we could write the following:

Button.prototype.cleanUp=function(){

this.domEl.buttonObj=null;

this.domEl=null;

}

308CHAPTER 8

Performance

The first line removes the reference that the DOM node has on this object. The second line removes this object’s reference to the DOM node. It doesn’t destroy the node but simply resets this local reference to the node to a null value. The DOM node was passed to our object as a constructor argument in this case, so it isn’t our responsibility to dispose of it. In other cases, though, we do have that responsibility, so let’s see how to handle it.

Disposing of DOM elements

When working with Ajax, and with large domain models in particular, it is common practice to construct new DOM nodes and interact with the document tree programmatically, rather than just via HTML declarations when the page first loads. Our ObjectViewer from chapters 4 and 5 and the notifications framework in chapter 6, for example, both contained several domain model objects capable of rendering themselves by creating additional DOM elements and attaching them to a part of the main document. With this great power comes great responsibility, and, for each node created programmatically, good housekeeping rules dictate that we are obliged to see to its disposal programmatically as well.

Neither the W3C DOM nor the popular browser implementations provide a way of destroying a DOM node outright once it has been created. The best we can do in destroying a created DOM node is to detach it from the document tree and hope that the garbage-collection mechanism in the browser will find it.

Let’s look at a straightforward example. The following script demonstrates a simple pop-up message box that uses the DOM to find itself using document.getElementById() when being closed:

function Message(txt, timeout){

var box=document.createElement("div"); box.id="messagebox"; box.classname="messagebox";

var txtNode=document.createTextNode(txt); box.appendChild(txtNode); setTimeout("removeBox('messagebox')",timeout);

}

function removeBox(id){

var box=document.getElementById(id); if (box){

box.style.display='none';

}

}

JavaScript memory footprint

309

 

 

When we call Message(), a visible message box is created, and a JavaScript timer is set to call another function that removes the message after a given time.

The variables box and txtNode are both created locally and go out of scope as soon as the function Message() has exited, but the document nodes that are created will still be reachable, because they have been attached to the DOM tree.

The removeBox() function handles the job of making the created DOM node go away when we're done with it. We have several possible options for doing this, from a technical standpoint. In the example above, we removed the box simply by hiding it from view. It will still occupy memory when invisible, but if we are planning on redisplaying it soon, that won’t be a problem.

Alternatively, we could alter our remove() method to dislocate the DOM nodes from the main document and hope that the garbage collector spots them before too long. Again, though, we don’t actually destroy the variable, and the duration of its stay in memory is outside our control.

function removeBox(id){

var box=document.getElementById(id); if (box && box.parentNode){

box.parentNode.removeChild(box);

}

}

We can discern two patterns for GUI element removal here, which we will refer to as Remove By Hiding and Remove By Detachment. The Message object here has no event handlers—it simply appears and disappears at its own speed. If we link the domain model and DOM nodes in both directions, as we did for our Button object, we would need to explicitly invoke the cleanUp() function if we were using a Remove By Detachment pattern.

Both approaches have their advantages and disadvantages. The main deciding factor for us is to ask whether we are going to reuse the DOM node at a later date. In the case of a general-purpose message box the answer is probably “yes,” and we would opt for removal by hiding. In the case of a more specific use, such as a node in a complex tree widget, it is usually simpler to destroy the node when finished with it than to try to keep lots of references to dormant nodes.

If we choose to use Remove By Hiding, we can adopt a complementary approach of reusing DOM nodes. Here, we modify the message-creation function to first check for an existing node and create a new one only if necessary. We could rewrite our Message object constructor to accommodate this:

function Message(txt, timeout){

var box=document.geElementById("messagebox"); var txtNode=document.createTextNode(txt);

310CHAPTER 8

Performance

if (box==null){ box=document.createElement("div"); box.id="messagebox"; box.classname="messagebox"; box.style.display='block'; box.appendChild(txtNode);

}else{

var oldTxtNode=box.firstChild; box.replaceChild(txtNode,oldTxtNode);

}

setTimeout("removeBox('messagebox')",timeout);

}

We can now contrast two patterns for GUI element creation, which we will refer to as Create Always (our original example) and Create If Not Exists (the modified version above). Because the ID that we check for is hard-coded, only one Message can be shown at a time (and that is probably appropriate here). Where we have attached a domain model object to a reusable DOM node, that domain object can be used to fetch the initial reference to the DOM node, allowing Create If Not Exists to coexist with multiple instances of an object.

NOTE When writing an Ajax application, then, it is important to be aware of memory-management issues regarding DOM elements, as well as conventional variables that we create ourselves. We also need to take account of the managed nature of DOM elements and treat their disposal differently. When mixing DOM nodes and ordinary variables, the use of cleanup code is advised, to break cyclic references.

In the following section, we’ll look at further considerations that the Ajax programmer needs to take into account when working with Internet Explorer.

Further special considerations for Internet Explorer

Each web browser implements its own garbage collector, and some work differently than others. The exact mechanisms of the Internet Explorer browser garbage collection are not well understood, but, according to the consensus of the comp.lang.JavaScript newsgroup, it has specific difficulties with releasing variables where a circular reference exists between DOM elements and ordinary JavaScript objects. It has been suggested that manually severing such links would be a good idea.

To describe this by example, the following code defines a circular reference:

function MyObject(id){ this.id=id;

this.front=document.createElement("div");