Thursday, March 1, 2007

JavaScript Closures

Motive:

JavaScript is a pretty powerful, flexible language. When worked with properly, code can be quickly and easily reused across different web pages and sites, or even a Firefox extension. (Such reuse can even extend to the Windows desktop using the Windows Script Host, and server-side web processing in Microsoft's Active Server Pages.)

However, like any other programming or scripting language, attention must be given to organization and structure, and overall, just writing "clean code". In my opinion, such focus is critical to meeting the goals of reuse and maintainability.

Java seems to promote structured code through the use of packages (namespaces) and classes. JavaScript doesn't currently have or promote the same approaches. As such, I often see JavaScript where no attempt was made to "package" various functionalities, resulting in excessive top-level variables, naming conflicts, and other undesirable issues.

For example, there are a number of code "libraries" meant for reuse where every variable and function is arbitrarily prefixed, e.g. function AB_myfunction() or var ACME_counter. At least this is an attempt to keep unique names, but what guarantee is there that there won't be any additional code that needs to share the same scope and happens to be named with the same conventions?

One Solution:

Take a look at JSON (Wikipedia), the JavaScript Object Notation. While JSON's primary purpose is a format for Ajax and other data-interchange uses, it can also work to "package" together JavaScript variables and functions.

JSON looks very much like a JavaScript array, just with each entry prefixed by a name and a colon. Here's a quick example of 2 variables and a function written with JSON:

var msgGenerator = {
  name: "visitor",
  location: "someplace unknown",
  alertHello: function(){
    alert("Hello, " + name
      + ", from " + location + "!");
  }
}

This can then be used like any other JavaScript object/function:

// Optionally customize the message.
msgGenerator.name = "Mark";
msgGenerator.location = "Wausau, WI";

msgGenerator.alertHello();

Assume that this "class" was much longer and more complex, and I needed to include it into an existing page. Even if the page already had "name" and "location" variables defined, here they are properly encapsulated and will be unaffected. Additionally, 'msgGenerator' could easily be renamed to anything else for whatever reason, for use within the page.

A Better Solution:

Utilize a "closure". First, consider that in JavaScript, "a function is an object is a variable". This allows for a function to be assigned to a variable, then called on the variable. Next, realize that unlike Java, functions can be nested within JavaScript.

Here I'll re-do the above example, this time using a closure:

var msgGenerator = function(){
  var name = "visitor";
  var location = "someplace unknown";

  function pub(){}

  pub.alertHello = function(){
    alert("Hello, " + name
      + ", from " + location + "!");
  }
  
  return pub;
}();

Note that here I've basically gained the use of public and private variables and functions, similar to Java. (name and location are private, while alertHello() is public.) This is not and never should be considered a security practice. It can help promote better code structure, partially by implicitly declaring which variables/functions are designed to be used outside the class, and which are designed for internal use only.

By declaring name and location as private, I can no longer customize them for displaying the message as I did above. You could either just make them public as well, or create "getters" and "setters", as is typical in Java.

Also, note the () at the end of the function. This returns the result of the function, which is the "pub" function. Without these parenthesis, msgGenerator.alertHello() would have to be called as msgGenerator().alertHello(). Doing this repeatedly could be "expensive", depending upon what operations are performed during the function's initialization. (It's also notable that msgGenerator.name returns "pub", a property and the name of the inner function, not the "visitor" value of the name variable.)

Reference:

You may find the following pages helpful for explaining the above, and for learning a few other JavaScript techniques:

Added 2007-10-01:

Security:

A quick extension to my security concern mentioned above. JavaScript is increasingly used throughout the web to enhance functionality and the user experience in a number of ways. However, remember that JavaScript runs on the client, and can be modified by the user using various tools, or simply disabled. Any validation or other critical processing that uses client-side JavaScript should also be repeated/verified by the web server.

2 comments:

Anonymous said...

However, running the 'closure' example with msgGenerator.alertHello(); returns "Hello, pub, from undefined!" (Ff3.6) and "Hello, undefined, from undefined!" (IE8)

Mark A. Ziesemer said...

Anonymous - the example is now fixed.