次の方法で共有


Closure Exposure: A JavaScript Scope Trick

You might have seen some JavaScript is wrapped up in funny looking functions, such as this screen shot, and wondered why – even parts of the Microsoft Ajax Framework previews use such an approach. At first it can seem very strange, but don't worry – it's actually very simple, and equally useful.

Scope

First of all, understand that JavaScript doesn't have "block scope" like C# or other languages do. Instead, the only scopes that it can handle are global (i.e. variables are attached to the "window" object), or function (i.e. variables declared within a function are only accessible within that function).

Next, consider that JavaScript functions are basically variables. They just happen to store data that is of the built in "Function" type (just like String, Date, Number, etc). Therefore, when you write JavaScript code like this;

function doSomething() {

// perform tasks

}

... you're actually defining a global variable called "doSomething" that is of type "Function". When you call it you're actually accessing that global variable – to prove it, I could equally define this function using one of many different constructs for functions (note: I'm not recommending this syntax – it can have other consequences);

var doSomething = new Function("// perform tasks");

... and call either definition using any of the following lines code;

doSomething();

window.doSomething();

window["doSomething"]();

Remember that object members can also be accessed using dictionary syntax in JavaScript, if the last of these looks a bit odd!

Debugging and Profiling tools

When debugging and profiling JavaScript, it is essential to be able to see where your code is spending it's time, or which function called another. When attaching event handlers, we can use easy anonymous function syntax such as this;

var button = Sys.get('#clickmebutton');

Sys.addHandler(button, 'click', function(e) {

// perform tasks

});

... but the problem with this is that when I use Internet Explorer 8 (or other tools) to profile this event handler shows as "Jscript – window script block". Of course, all anonymous functions show as the same, so tracking down which piece of code is a problem can be really difficult.

Instead, we could add a function name into this inline definition;

var button = Sys.get('#clickmebutton');

Sys.addHandler(button, 'click', function onClick(e) {

// perform tasks

});

We could also predefine the function and then attach a handler;

function onClick(e) {

// perform tasks

}

var button = Sys.get('#clickmebutton');

Sys.addHandler(button, 'click', onClick);

Such approaches mean that tooling can pick up on the function name, as seen in this screenshot;

The Problem

So what's the issue? How do these things add up to the use of this odd syntax? Well, the problem is that we often want to have many similarly named functions nowadays to help support debugging and profiling, but we don't want them to clash. They might be in code I write, or they might be in other frameworks. For example, if I define "onClick" in two different scripts, I don't want them to override each-other or cause a script error.

One way to handle this is using long function names, often driven from the namespace. So each name ends up as "Microsoft$Samples$Closures$TestComponent$onClick" (you can see examples of this in the screenshot too). The problem with this is twofold;

1. It doesn't protect different libraries from clashing if they fail to follow the guidelines.

2. Many tools truncate function name strings when reported... and even if they don't it is difficult to visualise which call we're in.

The Solution

Therefore, the solution relies upon the fact that JavaScript does enforce function scope. Any variable defined within a function is inaccessible outside that function. Secondly, we know that functions themselves are just variables – usually defined globally. So guess what – the trick is to use locally defined and hence scoped functions (as the "function" keyword is almost exactly an alias for "var newfunction = new Function....").

A Quick Walkthrough

Let's use some code to demonstrate the issue...

function closureScope() {

function innerFunction() {

// perform tasks

}

}

closureScope();

innerFunction();

Here I define innerFunction inside closureScope. I then call closureScope to ensure the definition has occurred. Finally I try to call innerFunction from the global scope. Run this code and it will fail – because innerFunction is undefined... because, of course, it is a definition local to closureScope.

What this means is that we can attach an event handler inside closureScope, name it "innerFunction", have this short name show up in tooling, but it will never clash with other definitions of innerFunction! We are effectively free to code within an isolated sandbox of sorts, exposing only the bits we want to by defining them as global, or attaching them to DOM events. Let's see that code;

function closureScope() {

function innerFunction() {

// perform tasks

}

Sys.addHandler(Sys.get('#clickmebutton'), 'click', innerFunction);

}

closureScope();

Nice huh? But there are two simplifications we can make;

1. We never ever need to refer to closureScope, and we don't want it to clash with other frameworks' closureScope encapsulation, so we'll just use an anonymous function;

function () {

function innerFunction() {

// perform tasks

}

Sys.addHandler(Sys.get('#clickmebutton'), 'click', innerFunction);

}

2. We can no longer call closureScope() afterwards as it has no name... so instead we'll wrap our function definition in brackets... remember that the function keyword returns a defined function... so we will then invoke it using the standard "()" parentheses;

(function () {

function innerFunction() {

// perform tasks

}

Sys.addHandler(Sys.get('#clickmebutton'), 'click', innerFunction);

}) ();

This might look clearer if we pass in a "window" object (which can be useful if we want to be able to mock window, for example);

(function (window) {

function innerFunction() {

// perform tasks

}

Sys.addHandler(Sys.get('#clickmebutton'), 'click', innerFunction);

}) (window);

Summary

This should explain why you see the "(function () { } )();" syntax in some JavaScript frameworks, and why you might consider starting to use it yourself. To have a try for yourself, download the attached HTML file (I've renamed it TXT so be sure to rename it back to HTML after download) and play with my scripts.

I have also highlighted one more optimisation in the attached... that is that the Microsoft Ajax Framework can use the Sys.require syntax to load in dependencies before executing a callback. Therefore, you can just use this anonymous callback function as the closure scope if you choose... have a look at the example and see what you think.

Finally... you'll notice I called this article "closure" exposure. Defining closures is beyond the energy left in my fingers after typing this up, but to get the most from this make sure you read up on them. I particularly like this logical walkthrough. Closures will almost certainly have an impact on how you write JavaScript code once you understand them.

Enjoy!

* One thing to note is that some minifiers remove function names where possible, forcing them to be anonymous functions in many situations. This is a good thing for release scripts as it saves download weight, but a) I prefer not to do this for debug scripts, and b) the closure approach is still powerful for other reasons – i.e. the isolation you get.

ClosureExposure.txt

Comments

  • Anonymous
    January 04, 2010
    Great trick! How about replacing Print() with print() to match the JS style standards?

  • Anonymous
    January 04, 2010
    @ Gilroy; I didn't define a print function... so I'm not sure what you mean? Simon

  • Anonymous
    January 05, 2010
    The comment has been removed

  • Anonymous
    January 05, 2010
    The comment has been removed

  • Anonymous
    February 03, 2010
    The comment has been removed

  • Anonymous
    March 03, 2010
    I was waiting since long time for explaination on how functions can be used in different ways and scope. Thanks for a great article on Javascript functions usage. Thanks, Rohan Reddy G.

  • Anonymous
    January 31, 2011
    Why not just use an anonymous function inside of the addHandler parameters? Sys.addHandler(button, 'click', function (e) {    // perform tasks });

  • Anonymous
    January 31, 2011
    @ Anon, for two reasons;

  1. As I said, I want my function names to appear in the tooling.
  2. Because I may need to make calls between locally scoped functions. What if your button handler needed to call another function? Hope that helps Simon