Digital Web Magazine

The web professional's online magazine of choice.

Objectifying JavaScript

Got something to say?

Share your comments on this topic with other web professionals

In: Columns > Behind the Curtain

By Jonathan Snook

Published on September 18, 2006

When I first began programming with JavaScript, I created my variables and then encapsulated any functionality I needed to reuse into a function. As my tasks got more complicated and as I started learning object-oriented programming in other languages, I began to see the benefits of applying that to JavaScript.

As scripts get larger, functions become more interrelated. Suddenly, you’ve got ten functions on a page, six of them calling each other to accomplish one task, and another four working towards something else entirely. For someone taking a first look at this code, it certainly wouldn’t be immediately clear which were meant to work together and which weren’t. This is where objects come in.

In a true object-oriented sense, an object represents a thing. It might be an image, a user, the document; anything referred to as a noun. An object consists of properties that describe the thing and methods that describe the actions the thing can perform (or that can be performed on it). For example, an array in JavaScript is an object. It has properties, such as length, that store information about the object; and methods, such as push, that do something with the object.

The important idea here is simply that objects encapsulate related functionality.

In JavaScript, we often perform a series of tasks on existing objects. Form validation, for example: “When this form is submitted, verify that the user has entered a valid e-mail address, phone number, etc.” Instead of writing a function to deal with each form on the page, why not bundle these related tasks into a FormValidator™ object that you can easily re-use on other pages and other sites? Even tasks like setting onclick events on a bunch of links may be better off contained within an object. Moreover, objects allow us to handle variable scope in a contained way, and simply keeps our code cleaner. Do you keep your cereal boxes all out on the counter? Or do you group them (alphabetically? by box size?) nicely on the same shelf in the cupboard?

We’ve established that objects are good and useful, but how do we go about creating them? JavaScript, a very flexible language, provides two basic methods for object creation, each with a number of implications and variations. The two basic methods we’ll discuss here are are:

Let’s shed some light on these options, and the advantages and disadvantages of each.

Using the Object Literal

Creating a new object using the object literal syntax is very straightforward. The object literal is encapsulated by curly brackets with zero or more property and value pairs, separated by commas. Each property and value is separated by a colon. Property names can be identifiers, strings, or numbers. Values can be strings, numbers, functions, or other objects. Property names get converted to strings, meaning that the string “25” and the integer 25 boil down to the same thing: Whichever gets declared last overwrites the other.

Example:

 {
  property: value,
  property: value
 }

Creating Objects

To demonstrate, let’s create a new object that has three properties; two store numeric values, while the other is an anonymous function (a function with no name):

 var AnimationManager = {
   framesPerSecond: 30,
   totalLength: 15,
   startAnimation: function(){ /* code goes here */ }
 }

It’s important to note that the object literal is really just a shortcut to creating objects using JavaScript’s built-in Object type. We’ll explore the code in more detail later, but for now just remember that we could also create the AnimationManager object with the following:

 var AnimationManager = new Object();
 AnimationManager.framesPerSecond = 30;
 AnimationManager.totalLength = 15;
 AnimationManager.startAnimation = function () { /* code goes here */ };

Accessing Object Properties

We’ve assigned the object literal to AnimationManager, and can now access the object’s properties using either dot notation or brackets. The following lines of code have exactly the same result:

 alert(AnimationManager.framesPerSecond); // object.property
 alert(AnimationManager['framesPerSecond']); // object['property']

We’ll likely use dot notation most of the time, but the option to treat the object like a hash is handy when we want to offer the flexibility to access similar properties in the same way. For example, if we needed a function to set either the framesPerSecond or totalLength property, we could either write:

 function changeValue(property, value) {
   if (property == "framesPerSecond") {
    AnimationManager.framesPerSecond = value;
   } else {
    AnimationManager.totalLength = value;
   }
 }

Or we could exploit the ability to access properties via brackets to shorten the logic:

 function changeValue(property, value) {
   AnimationManager[property] = value;
 }

Just like that, we’re able to reduce a complex series of if statements down to a single line.

Adding Properties or Methods

With our object now assigned to a variable, we can attach new properties or methods onto it at any time. Simply create a new property by using dot or bracket notation, just like you’d do to access an existing property:

 AnimationManager.stopAnimation = function(){ }
 AnimationManager.defaultTween = "sinoidal";

You’ll recall that is exactly what we did earlier when we discussed the alternate new Object() syntax for the object literal.

Using a Function

In JavaScript, a function is an object; creating a function creates an object. Function objects can be a little more useful than the object literals we just discussed, though, in that they can be used as a template for new objects.

For those unfamiliar with object-oriented programming, this object template is also known as a class. Instead of starting with a blank slate each time, objects built from a class are pre-populated with methods and properties. By defining a new function object, we are, in effect, defining a blueprint for new objects that we can use over and over again.

For example, we created an AnimationManager in the previous section using an object literal. We really only want one manager, so we don’t need to provide a mechanism for creating more; the object literal is a fine choice for this object. Let’s extend the example a bit, and assume that the AnimationManager controls multiple Animation objects. If we define Animation using an object literal, we’ll have to write the same code over and over again as we animate more elements in order to ensure that each new object has the proper methods and properties. Creating each animation object from a base template provides a solution for this problem.

Using a Template

To use our template to create a new object, we use the new keyword and call the template function, passing in arguments as usual:

 function Animation(element) {
   this.animationLength = 30;
   this.element = element;
 }
 
 var obj = document.getElementById('login');
 var animateLogin = new Animation(obj);

When new Animation(obj) is executed, a blank object is created in the background, and the Animation function is evaluated, referencing the new object as its this keyword. We can attach properties to the this keyword, and at the end of the function’s execution, the newly created object (along with all it’s properties) is returned. That means that the new object has two properties set, animationLength, and element, and gets assigned to the animateLogin variable for our later use.

We can also create methods in our templates, simply by assigning functions to properties of the this keyword, just like properties:

 function Animation(element) {
   this.animationLength = 30;
   this.element = element;
   this.onStart = function () {
    alert("The animation is starting!");
   };
   this.onEnd = function () {
    alert("The animation is ending!");
   };
 }
 
 var obj = document.getElementById('login');
 var animateLogin = new Animation(obj);

It’s important to note that we can add properties onto the Animation function itself, just like the object literal:

 function Animation() { }
 Animation.animationLength = 30;
 Animation.element = element;

The difference here is that these additional properties aren’t part of the template. If we executed new Animation() from the code above, we’d get back a blank object, not an object with two properties. For example:

 var animateLogin = new Animation(loginform);
 
 Animation.animationLength = 30;
 
 alert(animateLogin.element); // the 'loginform' element
 alert(animateLogin.animationLength); // undefined!

Prototype

The approach outlined above works wonderfully, but is a little wasteful. In particular, creating a thousand Animation objects would create a thousand copies of each method and property, taking up valuable memory we could be using for other things (and creating the potential for memory leaks). Luckily for us, there’s a way to create object templates that generate objects that all reference the same methods and properties: We attach properties to the template’s prototype. Doing so, any objects instantiated from the original template will be able to use the methods and properties from the template’s prototype instead of creating their own copies. In short, a call to a property of an object first checks the object itself for the property; if it doesn’t exist, it’ll check it’s template’s prototype; we’ll only have one version of the property stored in memory, no matter how many objects we create.

The syntax looks a lot like simply adding properties to the Animation object itself. Instead of Animation.onStart, we add properties to the function object’s prototype property, like so: Animation.prototype.onStart. Let’s rewrite the Animation object from above to make sure that it’s methods are referenced, not copied:

 function Animation(element){
   this.animationLength = 30;
   Animation.element = element;
 }
 Animation.prototype.onStart = function() {
   alert("The animation is beginning!");
 };
 Animation.prototype.onEnd = function() {
   alert("The animation is ending!");
 };
 var animateLogin = new Animation(loginform);
 animateLogin.onStart();

We can add to the template’s prototype at any time, and the new properties will automatically be available, even on object’s we’ve already created. Let’s rearrange the last example to demonstrate:

 function Animation(element){
   this.animationLength = 30;
   Animation.element = element;
 }
 var animateLogin = new Animation(loginform);
 
 Animation.prototype.onStart = function() {
   alert("The animation is beginning!");
 };
 Animation.prototype.onEnd = function() {
   alert("The animation is ending!");
 };
 
 animateLogin.onStart();

Even though we create the new Animation before we define the onStart and onEnd methods, we can still use them! This is a huge advantage of the prototype-based approach.

Returning an Object in the Constructor

As you are probably aware, functions can return values. What’s interesting is if you return an object as a result of a constructor (a function called with the new keyword), that object will get used as the new object instead of the blank object that new created and assigned to this. Any of the properties you’ve defined in or outside of the function will not be available. An example can probably demonstrate this better:

 function Animation(element){
   this.animationLength = 30;
   return {
    hello: "Hello, world!"
   }; // empty object.
 }
 Animation.prototype.onStart = function() { };
 Animation.prototype.onEnd = function() { };
 
 var animateLogin = new Animation(loginform);
 
 animateLogin.onStart(); // undefined!
 animateLogin.animationLength; // undefined!
 alert(animateLogin.hello); // "Hello, world!"

“What’s the point of that,” you ask? In a nutshell, this lets us set up private variables that are accessible inside our object, but not from the outside once it’s been created. Let’s take a look at an example using the new keyword on a function to see:

 function Animation(element){
   var looped = 0;
   var e = element;
 
   function construct() {
    this.loopCount = function() {
     return looped;
    }
    this.loop = function() {
     looped++;
    }
   }
   return new construct();
 }
 
 var animateLogin = new Animation();
 
 animateLogin.loop();
 
 alert(animateLogin.loopCount()); // it's 1
 alert(animateLogin.looped); // undefined!

We created a new function called construct within our Animation function. Anytime we create a new Animation object, we end up returning a new construct object instead. This means that the methods loopCount and loop will be the only ones we have access to. However, through the power of closures, looped is still available inside those functions. We’ve hidden away the internal mechanisms of the loop counter, ensuring that it can only be accessed in the ways we specify.

Singletons with Functions

There are times when we have one central object, and don’t really want or need to provide a mechanism for creating new versions of it. The singleton design pattern solves this problem. In the Animation object example we’ve been using, the AnimationManager is a central object that should control when animations start or stop. We only need one of these, as opposed to the individual animations that require separate Animation objects with unique values. We created the AnimationManager with an object literal, but you’ll often see singletons implemented with functions, let’s explore how:

 var AnimationManager = new function(){
 this.framesPerSecond = 30;
 this.startAnimation = function(){ /* code goes here*/ }
 }

We simply call new on an anonymous function, and use the this keyword to set up properties and methods. Creating a new object in this way removes the potential of using the object as a template. Voila, a singleton! If we want to be extra cautious about access to the object’s properties, we can even create private variables for our singleton using the same technique as the previous section.

 var AnimationManager = new function(){
 var framesPerSecond = 30;
 
 function construct(){
  this.startAnimation = function() {}
  this.getFPS = function() { return framesPerSecond; }
  this.setFPS = function(fps) { framesPerSecond = fps; }
 }
 
 return new construct();
 }

The advantage to this technique over the object literal is the ability to perform tasks when the singleton is being instantiated. None of the functions is evaluated at the time of instantiation when using the object literal. This requires an extra step to create an initialization function and run it immediately after it has been created.

The Object Factory

Another advanced pattern that you might see from time to time is a factory pattern. Without going into too much detail, we can use a function to create objects for us, which can be handy for handling object properties. Let’s take a quick look at an example:

 function objectMaker()
 {
 return {value:5}
 }
 var object1 = objectMaker();
 var object2 = objectMaker();

It’s handy because we can selectively overwrite values by mapping the values of one literal onto another. The Prototype JavaScript Framework (not to be confused with the prototype property) does exactly this with it’s Object.extend method.

Building Blocks

With JavaScript seeing a resurgence in popularity these days, knowing different approaches to object creation can make your code easier to read, understand and extend. Objects are a building block of maintainable JavaScript and a great asset to any JavaScript developer.

Resources

Got something to say?

Share your comments  with other professionals (0 comments)

Related Topics: Scripting, Programming, DOM

 

Jonathan Snook is a freelance web developer and consultant. When not working on one project or another, this proud father can be found spending time with his son and wife in beautiful Ottawa, Ontario, Canada.

Media Temple

via Ad Packs