Friday, January 04, 2013

JavaScript - The Good Parts - Part 4

Introduction

This is the fourth post based on the book JavaScript - The Good Parts by Douglas Crockford. The previous post covered some features of functions in JavaScript. This post takes this forward and covers more features of functions in JavaScript. The key concepts are Closures and Inner Functions which are very powerful features available to programmers of JavaScript.

Closure and Inner Functions

The "inner functions" get access to all variables and parameters of the function they are defined within, with the exception of "this" and "arguments". We saw example of this in the previous post.
Since a function is also a variable in JavaScript the values of the variables in a JavaScript function remains with the function for the lifetime of the function. A lifetime of the function depends on how the function is invoked. When a function is declared in the global scope and is invoked the lifetime of the function is limited to its execution time. Once the function is executed the function dies. When a function is used to create an object the lifetime extends to the lifetime of the variable. If this variable is at a global level then the lifetime of the function is equal to the lifetime of the document.
When a function is passed as a variable to another function, the lifetime of the function extends to the lifetime of the function that is being invoked. The function carries with it any values within it.
Consider a situation where we need to walk through all the nodes in the DOM of an HTML and invoke a function on the element. We need to recursively move through the elements and it is children and invoke the function on each of the element.
var WalkTheDOM = function (node, func) {
    func(node); //Invoke the function with the node as the parameter
    node = node.firstChild;
    while (node) {
        WalkTheDOM(node, func);
        node = node.nextSibling;
    }
};
Note that the right value of "node" is available to the inner function.
Let us now use this to identify all the elements with a specified "attribute" with a specified "value".
var getElementsByAttribute = function (attr, value) {
    var results [];
    WalkTheDOM (document, function () {
       var actual = node.nodeType === 1 && node.getAttribute(attr);
       if (typeof actual === "string" && (actual === value || typeof value !== "string")) {
           results.push(node);
       }
    });
}
Note how the variable "results" continues to be available to the function passed to WalkTheDOM. This is called closure.

Modules: Hiding State Value and Functions (Uses of Closure)

A typical way of implementing a counter would be to define a function as follows:
var Counter = function () {
    var currentValue = 0;

increment = function (inc) { this.currentValue = this.currentValue + (typeof inc === "number" ? inc : 1); } decrement = function (dec) { this.currentValue = this.currentValue - (typeof dec === "number" ? dec : 1); } getValue = function() { return this.currentValue; } }
To create counters we could do the following
var counterA = new Counter();
var counterB = new Counter();
counterA.increment();
counterB.decrement();
But implementing in this fashion leaves the variable currentValue accessible from outside.
For example one could do
counterA.currentValue = 10;
Ideally the currentValue should be inaccessible from outside of the Counter object. This can be achieved by rewriting the Counter function as below.
var Counter = function () {
    var currentValue = 0;
    return {
        increment: function (inc) {
            this.currentValue = this.currentValue + (typeof inc === "number" ? inc : 1);
 },
        decrement:   function (dec) {
           this.currentValue = this.currentValue - (typeof dec === "number" ? dec : 1);
        },
        getValue = function () {
           return this.currentValue;
        }
    };
}();
What is being done is that Counter function returns an object which contains the increment and decrement functions, but the outside world cannot access the currentValue variable. We can now instantiate counters as.
var counterA = new Counter();
var coutnerB = new Counter();
counterA.increment();
counterB.decrement();
And
counterA.currentValue will be invalid.
In a similar manner we can hand hide functions. Any function that is not returned as part of the object will be private to the global function and can be accessed from anywhere within that function.

Using setTimeout with Closure

Consider a use case where it is required to fade out a node slowly over a period of time. One will need to use the setTimeout function of JavaScript to slowly reduce the opaqueness of the node. But the problem that arises is how to keep the value of the node between two calls invoked by setTimeout. The easiest way out is to define a global variable which has this node and the setTimeout will function on this global variable. But what if one needs to fade out more than one node at a time? This can be solved elegantly by using closure as given below:
var fade = function (node) {
    var level = 1;
    var step = function () {
        var hex = level.toString(16);
        node.style.backgroundColor = "#FFFF" + hex + hex;
        if (level < 15) {
            level = level + 1;
            setTimeout(step, 100);
        }
    }
}
fade ();
fade ();
fade();
The value of level and node is retained and carried forward from one call to another without the use of a global variable.

Cascade

Relook at the Counter function.
var Counter = function () {
var currentValue = 0;
    return {
        increment:   function (inc) {
            this.currentValue = this.currentValue + (typeof inc === "number" ? inc : 1);
        },
        decrement: function (dec) {
            this.currentValue = this.currentValue – (typeof dec === "number" ? dec : 1);
        },
        getValue = function () {
            return this.currentValue;
        }
    }
}();
To use the functions in this we need to do the following:
var counterA = new Counter();
counterA.increment(2);
counterA.getValue();
So if one needs to invoke two different functions one needs to put it in two different lines. Instead if we rewrite the function as
var Counter = function () {
    var currentValue = 0;
        return {
            increment:   function (inc) {
            this.currentValue = this.currentValue + (typeof inc === "number" ? inc : 1);
            return this;
        },
        decrement: function (dec) {
            this.currentValue = this.currentValue - (typeof dec === "number" ? dec : 1);
     return this;
        },
        getValue = function () {
            return this.currentValue;
        }
    }
}();
We can now do this
counterA.increment().getValue();
This makes coding much easier and simpler. This is called cascading. Returning the object in methods that do not return anything else is called cascading.

No comments: