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

302CHAPTER 8

Performance

know what the expected returns of such an operation would be. It might be tempting to conclude that DOM operations are roughly eight times more costly than pure JavaScript calculations, but that holds true only for this specific example. You may well find that to be the case in many situations, but a rule of thumb is best supplemented by a few measurements—and preferably on a range of different machines and browsers.

We won’t spend more time now on profiling and execution speed. The examples that we have run through should give you a feel for the benefits that profiling can provide on your Ajax projects. Let’s assume that your code is running at a satisfactory speed thanks to a bit of profiling. To ensure adequate performance, you still need to look at the amount of memory that your application is using. We’ll explore memory footprints in the next section.

8.3 JavaScript memory footprint

The purpose of this section is to introduce the topic of memory management in Ajax programming. Some of the ideas are applicable to any programming language; others are peculiar to Ajax and even to specific web browsers.

A running application is allocated memory by the operating system. Ideally, it will request enough to do its job efficiently, and then hand back what it doesn’t need. A poorly written application may either consume a lot of memory unnecessarily while running, or fail to return memory when it has finished. We refer to the amount of memory that a program is using as its memory footprint.

As we move from coding simple, transient web pages to Ajax rich clients, the quality of our memory management can have a big impact on the responsiveness and stability of our application. Using a patterns-based approach can help by producing regular, maintainable code in which potential memory leaks are easily spotted and avoided.

First, let’s examine the concept of memory management in general.

8.3.1Avoiding memory leaks

Any program can “leak” memory (that is, claim system memory and then fail to release it when finished), and the allocation and deallocation of memory are a major concern to developers using unmanaged languages such as C. JavaScript is a memory-managed language, in which a garbage-collection process automatically handles the allocation and deallocation of memory for the programmer. This takes care of many of the problems that can plague unmanaged

JavaScript memory footprint

303

 

 

code, but it is a fallacy to assume that memory-managed languages can’t generate memory leaks.

Garbage-collection processes attempt to infer when an unused variable may be safely collected, typically by assessing whether the program is able to reach that variable through the network of references between variables. When a variable is deemed unreachable, it will be marked as ready for collection, and the associated memory will be released in the next sweep of the collector (which may be at any arbitrary point in the future). Creating a memory leak in a managed language is as simple as forgetting to dereference a variable once we have finished with it.

Let’s consider a simple example, in which we define an object model that describes household pets and their owners. First let’s look at the owner, described by the object Person:

function Person(name){ this.name=name; this.pets=new Array();

}

A person may have one or more pets. When a person acquires a pet, he tells the pet that he now owns it:

Person.prototype.addPet=function(pet){

this.pets[pet.name]=pet; if (pet.assignOwner){

pet.assignOwner(this);

}

}

Similarly, when a person removes a pet from his list of pets, he tells the pet that he no longer owns it:

this.removePet(petName)=function{ var orphan=this.pets[petName]; this.pets[petName]=null;

if (orphan.unassignOwner){ orphan.unassignOwner(this);

}

}

The person knows at any given time who his pets are and can manage the list of pets using the supplied addPet() and removePet() methods. The owner informs the pet when it becomes owned or disowned, on the assumption that each pet adheres to a contract (in JavaScript, we can leave this contract as implicit and check for adherence to the contract at runtime).

304CHAPTER 8

Performance

Pets come in several shapes and sizes. Here we define two: a cat and a dog. They differ in the attitude that they take toward being owned, with a cat paying no attention to whom it is owned by, whereas a dog will attach itself to a given owner for life. (I apologize to the animal world for gross generalization at this point!)

So our definition of the pet cat might look like this:

function Cat(name){ this.name=name;

}

Cat.prototype.assignOwner=function(person){

}

Cat.prototype.unassignOwner=function(person){

}

The cat isn’t interested in being owned or disowned, so it provides empty implementations of the contractual methods.

We can define a dog, on the other hand, that slavishly remembers who its owner is, by continuing to hold a reference to its master after it has been disowned (some dogs are like that!):

function Dog(name){ this.name=name;

}

Dog.prototype.assignOwner=function(person){

this.owner=person;

}

Dog.prototype.unassignOwner=function(person){

this.owner=person;

}

Both Cat and Dog objects are badly behaved implementations of Pet. They stick to the letter of the contract of being a pet, but they don’t follow its spirit. In a Java or C# implementation, we would explicitly define a Pet interface, but that wouldn’t stop implementations from breaching the spirit of the contract. In the real world of coding, object modelers spend a lot of time worrying about badly behaved implementations of their interfaces, trying to close off any loopholes that might be exploited.

Let’s play with the object model a bit. In the script below, we create three objects:

1jim, a Person

2whiskers, a Cat

3fido, a Dog

JavaScript memory footprint

305

 

 

First, we instantiate a Person (step 1):

var jim=new Person("jim");

Next, we give that person a pet cat (step 2). Whiskers is instantiated inline in the call to addPet(), and so that particular reference to the cat persists only as long as the method call. However, jim also makes a reference to whiskers, who will be reachable for as long as jim is, that is, until we delete him at the end of the script:

jim.addPet(new Cat("whiskers"));

Let’s give jim a pet dog, too (step 3). Fido is given a slight edge over whiskers in being declared as a global variable, too:

var fido=new Dog("fido"); jim.addPet(fido);

One day, Jim gets rid of his cat (step 4):

jim.removePet("whiskers");

Later, he gets rid of his dog, too (step 5). Maybe he’s emigrating?

jim.removePet("fido");

We lose interest in jim and release our reference on him (step 6):

jim=null;

Finally, we release our reference on fido, too (step 7):

fido=null;

Between steps 6 and 7, we may believe that we have gotten rid of jim by declaring him to be null. In fact, he is still referenced by fido and so is still reachable by our code as fido.owner. The garbage collector can’t touch him, leaving him lurking on the JavaScript engine’s heap, taking up precious memory. Only in step 7, when fido is declared null, does Jim become unreachable, and our memory can be released.

In our simple script, this a small and temporary problem, but it serves to illustrate that seemingly arbitrary decisions affect the garbage-collection process. Fido may not be deleted directly after jim and, if he had the ability to remember more than one previous owner, might consign entire legions of Person objects to a shadow life on the heap before being destroyed. If we had chosen to declare fido inline and the cat as a global, we wouldn’t have had any such problem. To assess the seriousness of fido’s behavior, we need to ask ourselves the following questions: