Month: May 2015

The (Proto)Typical JavaScript Function

We’ve seen that constructor functions can be used to initialize JavaScript objects in a consistent manner. The constructor can be used to ensure that an object is always initialized with a known set of properties and default values for those properties. But it’s also possible to assign functions to object properties from within a constructor function.

function Neighbor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = function () {
        return this.firstName + " " + this.lastName;
    }
}

var friend = new Neighbor("Tom", "Smith");
var name = friend.fullName();

Assigning a function to the fullName property provides a convenient way to access a Neighbor's full name. However, using an anonymous function is an inefficient way to add this functionality. If I create another Neighbor instance, JavaScript will create another instance of this anonymous function.

var friend1 = new Neighbor("Tom", "Smith");
var name1 = friend1.fullName();

var friend2 = new Neighbor("Sally", "Jones");
var name2 = friend2.fullName();

The sample code runs as expected, but if we compare instances of the fullName property, we can see they do not reference the same function. Every time we execute our constructor function, JavaScript creates a new instance of the anonymous function and assigns it to the fullName property.

if (friend1.fullName !== friend2.fullName) {
    alert("The fullName properties are NOT identical.");
}

Creating one extra instance of this anonymous function may not be a big deal, but what if our object has a half-dozen functions and we have hundreds of Neighbor instances? Each instance needs to be created, which requires some amount of time and memory.

One solution would be to simply define a named function outside of our constructor function.

function Neighbor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = concatenateFullName;
}

function concatenateFullName() {
    return this.firstName + " " + this.lastName;
}

It may look a bit strange, but it does work. However, our function is no longer tightly coupled to the Neighbor object, which brings us to the JavaScript prototype function. All JavaScript objects have a prototype property that allows us to attach new properties, not just to an object instance, but to a specific object type. In this case, we can associate a fullName property with the Neighbor type.

function Neighbor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

Neighbor.prototype.fullName = function () {
    return this.firstName + " " + this.lastName;
}

Now, all objects created with the Neighbor constructor function will have an associated fullName property through Neighbor's prototype. If we create two Neighbor instances, they will share a common fullName function. When we reference friend.fullName, JavaScript will look for a fullName property on the friend object, but if it doesn’t exist, it will also look for a fullName property on the prototype of the Neighborconstructor function that was used to create the object.

if (friend1.fullName === friend2.fullName) {
    alert("The fullName properties are identical.");
}

Of course, prototype properties don’t have to reference functions. They can also be used to share values across object instances.

function Tank(level) {
    this.level = level;
}

function.prototype.alertLevel = 10;

function.prototype.isLevelSafe = function () {
    return this.level >= this.alertLevel;
}

var tank = new Tank(100);
var isSafe = tank.isLevelSafe(); // returns true
tank.level = 2;
isSafe = tank.isLevelSafe(); // returns false

Prototypes allow us to associate a single property instance with all instances of a particular object type, which gives us the ability to extend an object’s functionality beyond what’s simply provided within it’s constructor function.

Constructing Constructor Functions

When you are first learning JavaScript, you will be introduced to two ubiquitous language features: functions and objects. As you begin to experiment with creating objects, you may find is useful to implement a simple function to create commonly used object types. For instance, here’s a function that will create an instance of a photo object:

function createPhoto(url, date, caption) {
    return {
        url: url,
        date: date,
        caption: caption
    };
}

var photo = createPhoto("rabbits.jpg", "05/10/2015",
                       "Someone's been eating my radishes.");

Using such a function ensures that every photo object has the same set of properties, and this example provides an excellent starting point for understanding JavaScript constructor functions. What if we make a few minor changes…

function initPhoto(newPhoto, url, date, caption) {
    newPhoto.url = url,;
    newPhoto.date = date;
    newPhoto.caption = caption;
}

var photo = {};
initPhoto(photo, "rabbits.jpg", "05/10/2015",
          "Someone's been eating my radishes.");

Instead of creating the object within our function, we create the object and pass it to the function for initialization. But the end result is the same. We have a new object with all the expected properties attached. This is essentially how constructor functions work, but the syntax is slightly different.

function Photo(url, date, caption) {
    this.url = url;
    this.date = date;
    this.caption = caption;
}

var photo = new Photo("rabbits.jpg", "05/10/2015",
                      "Someone's been eating my radishes.");

When we use the new keyword, JavaScript creates a new object and passes it into the constructor function. However, instead of appearing in the function’s argument list, it’s accessed using the this keyword. By convention, constructor functions are capitalized to distinguish them from other functions.

Once you feel comfortable with how constructor functions are used to initialize new objects, you can begin to experiment with some of their other benefits – prototypes.

Two Quick Steps to JavaScript Events

If you’re just starting out with JavaScript and want to respond to an event on the page, you can keep these two simple steps in mind. For this example, we will look at responding to a click event on an anchor tag.

<a id="click-target" href="redirect.html">Click here.</a>

Including an id attribute will allow us to easily find the element within the page.

1. Listen for an event.

We first find the anchor tag, and begin listening for its click event. Listening is accomplished by calling the element’s addEventListener method and providing a callback function.

var anchor = document.getElementById("click-target");
anchor.addEventListener("click", handleClickEvent);

Or we can use the element’s onclick property to specify the event callback function.

var anchor = document.getElementById("click-target");
anchor.onclick = handleClickEvent;

2. Implement a callback function.

An event listener always receives an Event object that describes the event. For our example, we will receive this object as the first argument in the callback function, and reference it with the variable e.

function handleClickEvent(e) {
    // Respond to the click event.
    alert("Anchor was clicked!");

    // The default action when clicking an anchor tag is to
    // redirect the browser to the URL specified in the
    // href attribute. If we want to prevent the redirect,
    // we can use the Event object to prevent the default
    // behavior.
    e.preventDefault(); 
}