Saturday, January 23, 2010

Closures and Objects in JavaScript


"If you want to make an apple pie from scratch, you must first create the universe." - Carl Sagan

I've always hated the word "JavaScript". What's up with that capital S? However I've always loved the language. I learned the basics of it when Netscape 3 came out in 1996, and was immediately hooked. Your web browser became a scripting language interpreter, how cool! Before then most web pages were static, and business websites, if they existed at all, were mainly shells listing contact information and displaying the company logo.

Some pages I stumbled on back then used CGI scripts to take user input and do something with it - private content that required a login, rudimentary chat, surveys like the ever-popular "purity test", Pi calculators (show me __ digits of Pi [Submit]), things like that. The scripts were all server-side, though. The code was being executed from private space in the web server's cgi-bin directory. For people relatively new to the web like myself, doing something like this was an impossibility. We hadn't found Linux or Apache yet, hadn't learned perl or shell scripting, and our web hosts were angelfire, geocities, tripod, and CompuServe and AOL's user page areas. We could do chili recipes, pictures of our cats, and link to other people's recipes and cat pictures, but not write anything interactive.

From the professional programmers of 1996, Javascript was immediately scorned as a toy for little kids playing progammer. For those of us with a little insight who didn't have cgi-bin access on the web host we were using, Javascript was nothing less than a godsend. We struggled to do client-side what could previously only be done by a script on the webserver. We begged for more features from Netscape. Microsoft struggled to copy the functionality with JScript. Standards bodies struggled to make a standard language definition out of it. Good times.


My biggest early success with Javascript was my first version of Unlog. Unlog translated the hexadecimal log output from the Cleo 3780 software (and Gentran Server NT, and later Gentran Director, which used API calls to Cleo) into the characters the hex values represented. I could write dozens of pages easy on Unlog, what tech problem it was solving, and it's evolution, but the short story is I built it to help my wife speed up troubleshooting the comm problems her bisync customers were having, and ultimately it led to me getting hired at Sterling Commerce at $10k more than what I was making at CompuServe. Version 1? JavaScript on a web page. Paste in your log to a textarea field, click "translate", cross your fingers. Later versions were in VB for the user interface, calling a C .dll file to do the translation, and actually translated everything correctly. And quickly. Version 1 was slower than crap and buggy, but much faster than the paper-and-pencil method Teresa was using.

With that lead-in, let me say that I just figured out how to get blogger to work with Javascript files, which is great because not only can I can post some old goodies like my Connect-4 bot (code for said bot is on my dead computer's hard-drive), but I can start posting code snippets that you can actually see working, instead of just seeing my screenshots. So I'm going to shy away from talking about awesome, awesome perl for a short while, and go on at length about Javascript. Woo-hoo!

I found out recently that Javascript supports closures, which I wasn't aware of. As I became a professional coder I stopped doing much in the way of hobbyist web development, I lost touch with changes in HTML, enhancements to Javscript, and until recently didn't have a good grasp on what the whole "ajax" business was about, and now I'm trying to catch back up.

What's a "closure"? Purists will try to shame me or belittle my understanding when I say this, but basically a closure is a function within a function. It allows local variables created by the "parent" function to stay in existence and be manipulated by the child functions. Why is this a good thing? It lets you both manage the state of local variables and make them visible to the rest of the program. It lets you write something complicated that can be reused by someone else without worrying about stepping on their variables. Consider:

A variable is visible to everything lower in the heirarchy of where it was created, but nowhere higher...
var a = 5;
if (some_condition == true) {
var b = 5;
while (b > 0) {
var c = 5;
do_something;
b--;
}
}
The "a" variable is visible everywhere. "b" is not visible outside of the larger "if" block, but is visible everywhere inside it, including the smaller "while" block. "c" is only visible inside the "while" block, but nowhere higher. Congratulations! Now you understand all there is to know about variable scope. "a" is global, everything else is local to the area where it is defined. Variables defined in a function are only visible inside the function.

So what are your options if you want a function to manage the state of a variable, say, a counter? The counter can be local to the function, in which case after the function returns, the variable is destroyed -- no more counter. The counter can be global, which would keep it from getting destroyed after the function exits, but nothing prevents the rest of the script from modifying the counter, intentionally or accidentally. It's not the function's counter, it's the whole script's counter. This means that the only effective way to manage the state of a variable important to the entire script is in the main block. "Here, import this function to do x" becomes "here, paste this into the main block of your code." Absurd.

This is where closures come into play. With closures, you can create variables local to a function that don't disappear after the function returns, and can keep being manipulated by it. Here is our simple counter example, written with closures:
function init_closure() {
var counter = 0;
increment = function() { counter++; }
decrement = function() { counter--; }
showCounter = function() { alert("Counter is " + counter); }
}
A call to init_closure creates the counter. Calls to "increment" and "decrement" change its value. There is nothing else in the script that can directly touch this variable's counter. Here is some sample Javascript to initialize the counter, increment it three times, decrement it once, then show the current value in an alert box. Give it a whirl:
function demo_closure() {
init_closure();
increment();
increment();
increment();
decrement();
showCounter();
}
Closure demonstration

Object-oriented programming works on similar principles. Instead of just being able to manage the state of variables, a language that supports objects can have functions that create permanent variables that contain a collection of other variables. If I want to run an online bookstore, I can create a variable for a book that contains a title, author, and price. Not only that, but I can create more than one book "object", giving each of them different variables. This is all managed with the magic words "this" and "new". Here is an example of a simple function that returns a permanent book object:
function book(x,y,z) {
this.title = x;
this.author = y;
this.price = z;
}
Call this function with three variables, and it returns an object with those variables turned into title, author and price. This type of function is called a "constructor". To call a constructor, you include the prefix "new" before calling it. For example:
var my_awesome_book = new book("Innumeracy","John Paulos",7);
What's more, other functions can manipulate the object. For example, let's have a 10% off sale:
function onSale(book) {
book.price *= .9;
}
Pass this function a book object, and it updates the book's price.

Here, create a couple books and display something them both:
function demo_objects() {
book1 = new book("Gravity's Rainbow", "Thomas Pynchon", 20);
book2 = new book("The Naked Lunch", "William Burroughs", 10);
alert("Book 1's title is: " + book1.title);
alert("Book 2's author is: " + book2.author);
}
Objects demonstration

Now put one of them on sale:
function demo_book_sale() {
alert("Price of " + book1.title + " before sale is: $" + book1.price);
onSale(book1);
alert("Price of " + book1.title + " after sale is: $" + book1.price);
}
Book sale

So the point to all this is the Javascript is no longer a toy language. It powers much of Google maps. It can make webservice calls and interpret the results, which is where Wikipedia gets its predictive text in the search field from, it's how Facebook lets you "show more" without reloading the page. It has real power, and passes the man or boy test ,which, incidentally, uses closures. :)
function A(k,x1,x2,x3,x4,x5) {
var B = function() { return A(--k, B, x1, x2, x3, x4) }
return k<=0 ? x4()+x5() : B()
}

function K(n) {
return function() { return n }
}

function man_or_boy() {
alert( A(10, K(1), K(-1), K(-1), K(1), K(0) ) )
}
Try the man or boy test. The correct answer is -67.

No comments:

Post a Comment