
|
JavaScript ClosuresThis tutorial takes a look at JavaScript closures. In JavaScript, this means is that an inner function always has access to the variables and parameters of its outer function, even after the outer function has returned. This is an extremely powerful property of the language.
In Douglas Crockford's article entitled “Private Members in JavaScript”, he uses this technique to produce private members, and privileged methods in a JavaScript object. Rather than repeat that work, I'll look at a pragmatic problem that appears many times in web applications – correct link formation.
The ProblemSimple links usually do not represent a problem. You can embed them directly in a document thus:
<a href='http://www.syger.it/Tutorials/UsingJavaScript.html'>
Using JavaScript
</a>
Links which require a query part, however, can become problematic:
<a href='http://finance.yahoo.com/q/bc?s=YHOO&t=5d'>
Ticker 'YHOO'.
</a>
Here, we have the URL (http://finance.yahoo.com/q/bc) and the query (s=YHOO&t=5d), which obviously could be modified to display a different company (the s parameter) for a different period of time (the t parameter). Clearly, we can use this information to provide links to all companies on the American stock market, for different lengths of time.
Let's say that this idea spurs us on to create a financial advice web site, where we want to reference different companies over different time periods, in several different pages. Simple hard coding would not be a sensible solution because Yahoo! Inc's financial service might change their URL, or parameter names. It could be that they keep the old URL for compatibility reasons, but create an entirely new URL which gives much better information. Possibly, we decide that Google Inc's financial service is better, but we find that it has a different URL (http://finance.google.com/finance), and query (q=YHOO).
The SolutionWe'd like to build a function which gives us the complete URL, which we can then paste into any position on our pages, as a link, but we don't want any of the component information in the URL to be available outside the function, to avoid polluting the pages with potentially dangerous information.
We can limit the damage by creating an object which has a series of fixed functions for each of the stocks we're interested in. For this example I'll use four; Yahoo! Inc., Google Inc., Microsoft Corporation, and Sun Microsystems Inc. The choice is purely casual, I'm not a financial advisor.
The Closure CodeThe Scripts/Closure.js file, explained in this tutorial, contains a single function which creates an object containing the link creating functions for our four stock tickers. As in the introspection tutorial, I've placed this function in a namespace:
var app = {
// ...
};
I'm assuming that as the application grows, so will the number of properties in the app namespace.
Then we have the createFinancialLinks function, which uses inner functions and closure:
/**
Creates the company financial information object.
Example usage:
<pre>
var refs = app.createFinancialLinks("http://finance.yahoo.com/q/bc",
["s", "t"],
{
"Yahoo" : "YHOO",
"Google" : "GOOG",
"Microsoft" : "MSFT",
"Sun" : "JAVA"
});
</pre>
@param url the financial service URL.
@param params an array of the query parameter names,
in order: ticker name, time span.
@param tickers a collection of the company identifier : ticker names.
*/
createFinancialLinks : function (url, params, tickers) {
function makeLinker(ticker) {
return function (span) {
var timeSpan = "";
if (span && params.length > 1 && typeof params[1] === "string") {
timeSpan = ["&", params[1], "=", span].join('');
}
return [url, "?", params[0], "=", ticker, timeSpan].join('');
};
}
var info = {};
for (var idx in tickers) {
if (tickers.hasOwnProperty(idx)) {
info["linkTo" + idx] = makeLinker(tickers[idx]);
}
}
return info;
}
To understand this code, I'll separate it into two parts; the inner function makeLinker, and the function body. Since it's the easy part, I'll start with the function body, which begins after the inner function definition. This code creates a collection called info which will contain a method linkToXXX for each of the tickers we supply as the tickers parameter. The XXX part will be replaced by the company identifier, such as linkToSun. This collection is then returned to the caller.
The real magic takes place inside the inner function makeLinker. Firstly, using an inner function can increase the readability of the code. Here, for example, it much simplifies the loop in the body of our createFinancialLinks function, without having to create another publicly visible function. Secondly, it has references to the parameters and local variables of its parent function, so they don't have to be passed as parameters, and you don't have to create public properties.
Our inner function makeLinker returns an anonymous function definition, which has one parameter, span. But this function definition also uses parameters from the createFinancialLinks function, and from makeLinker to be able to do its job. Since it is a function definition, it is not actually being called, but simply stored in our info object.
At some later time this function will actually be called, and will produce the desired results, almost magically. This is where closure comes in. Not only does JavaScript store the function definition, but it also stores the 'environment' - the parameters and variables which existed at the time the function was defined. This information is stored in the scope object for the function.
Using the CodeNow we can execute the example test code, provided in the Scripts/ClosureExample.js file, which is shown below:
// Create a short cut to the normal output device
if (!syger.exists(this, "puts")) {
var puts = function (str) {
document.write(str);
};
}
// Utility function to print the list
function printList(tickers) {
puts("<ul>\n");
for (var idx in tickers) {
if (tickers.hasOwnProperty(idx)) {
puts(" <li><a href='" + refs["linkTo" + idx]("5d")
+ "'>" + idx + "</a></li>\n");
}
}
puts("</ul>\n");
}
// Create references for Yahoo! Inc. financial services
var systemTickers = {
"Yahoo" : "YHOO",
"Google" : "GOOG",
"Microsoft" : "MSFT",
"Sun" : "JAVA"
};
var refs = app.createFinancialLinks("http://finance.yahoo.com/q/bc",
["s", "t"], systemTickers);
// Show the Yahoo! Inc. links
puts("<p>Links for tickers in ");
puts("<a href='" + refs.linkToYahoo("5d") + "'>Yahoo! Inc</a>");
puts(" financial services:</p>\n");
printList(systemTickers);
// Create references for Google Inc. financial services
refs = app.createFinancialLinks("http://finance.google.com/finance",
["q"], systemTickers);
// Show the Google links
puts("<p>Links for tickers in ");
puts("<a href='" + refs.linkToGoogle("5d") + "'>Google Inc</a>");
puts(" financial services:</p>\n");
printList(systemTickers);
You've already met the five lines of code at the start in the introspection tutorial. The printList function simply writes an unordered list of all the tickers. I created a function because I use this code twice in the example. Note that this function uses the index reference mechanism (object[property]) to refer to, and call, the linksToXXX functions, rather than dot notation (object.property).
Next, I provide the information to produce the links to the Yahoo! Inc., financial services.
Then I output a list, with the paragraph header using the dot notation to refer to the Yahoo! Inc., stock quote.
So far so good. But Google Inc., also provides financial services, so the next three lines creates a new set of link functions to this service, and finally I write out the same structure, but this time using Google's service.
Now we can run the example code, which gives the following results:
Note: if you don't see any lists in the bordered box above this line, you probably have JavaScript disabled in your browser. This is a live demonstration, so you'll need to enable JavaScript!
The next and final tutorial explains the XML generator library, starting with the design of the library.
All the scripts in these tutorials are available for download as two compressed archives; Scripts.zip and AspScripts.zip, both distributed under the GNU Lesser General Public License.
ContactsSyger can be contacted for consultancy work on any of the topics mentioned in this article, by sending an email to info@syger.it.
|
Tag cloud: |