Chapter 3. Functions

Mastering functions is an important skill when you learn any programming language and even more so when it comes to JavaScript. This is because JavaScript has many uses for functions, and much of the language's flexibility and expressiveness comes from them. Where most programming languages have a special syntax for some object-oriented features, JavaScript just uses functions. This chapter will cover:

  • How to define and use a function

  • Passing parameters to a function

  • Pre-defined functions that are available to you "for free"

  • The scope of variables in JavaScript

  • The concept that functions are just data, albeit a special type of data

Understanding these topics will provide a solid base that will allow you to dive into the second part of the chapter, which shows some interesting applications of functions:

  • Using anonymous functions

  • Callbacks

  • Self-invoking functions

  • Inner functions (functions defined inside functions)

  • Functions that return functions

  • Functions that redefine themselves

  • Closures

What is a Function?

Functions allow you group together some code, give this code a name, and reuse it later, addressing it by name. Let's see an example:

function sum(a, b) {
    var c = a + b;
    return c;
}

What are the parts that make up a function?

  • The function statement.

  • The name of the function, in this case sum.

  • Expected parameters (arguments), in this case a and b. A function can accept zero or more arguments, separated by commas.

  • A code block, also called the body of the function.

  • The return statement. A function always returns a value. If it doesn't return value explicitly, it implicitly returns the value undefined.

Note that a function can only return a single value. If you need to return more values, then simply return an array that contains all of the values as elements of this array.

Calling a Function

In order to make use of a function, you need to call it. You call a function simply by using its name followed by any parameters in parentheses. "To invoke" a function is another way of saying "to call".

Let's call the function sum(), passing two parameters and assigning the value that the function returns to the variable result:

>>> var result = sum(1, 2);
>>> result;

3

Parameters

When defining a function, you can specify what parameters the function expects to receive when it is called. A function may not require any parameters, but if it does and you forget to pass them, JavaScript will assign the value undefined to the ones you skipped. In the next example, the function call returns NaN because it tries to sum 1 and undefined:

>>> sum(1)

NaN

JavaScript is not picky at all when it comes to parameters. If you pass more parameters than the function expects, the extra parameters will be silently ignored:

>>> sum(1, 2, 3, 4, 5)

3

What's more, you can create functions that are flexible about the number of parameters they accept. This is possible thanks to the arguments array that is created automatically inside each function. Here's a function that simply returns whatever parameters are passed to it:

>>> function args() { return arguments; }
>>> args();

[]

>>> args( 1, 2, 3, 4, true, 'ninja'),

[1, 2, 3, 4, true, "ninja"]

By using the arguments array you can improve the sum() function to accept any number of parameters and add them all up.

function sumOnSteroids() { 
  var i, res = 0; 
  var number_of_params = arguments.length;
  for (i = 0; i < number_of_params; i++) {
    res += arguments[i];
  }
  return res; 
} 

If you test this function by calling it with a different number of parameters (or even no parameters at all), you can verify that it works as expected:

>>> sumOnSteroids(1, 1, 1);

3

>>> sumOnSteroids(1, 2, 3, 4);

10

>>> sumOnSteroids(1, 2, 3, 4, 4, 3, 2, 1);

20

>>> sumOnSteroids(5);

5

>>> sumOnSteroids();

0

The expression arguments.length returns the number of parameters passed when the function was called. Don't worry if the syntax is unfamiliar, we'll examine it in detail in the next chapter. We'll also see that arguments is technically not an array, but an array-like object.

Pre-defined Functions

There are a number of functions that are built into the JavaScript engine and available for you to use. Let's take a look at them. While doing so, you'll have a chance to experiment with functions, their parameters and return values , and become comfortable in working with them. The list of the built-in functions is:

  • parseInt()

  • parseFloat()

  • isNaN()

  • isFinite()

  • encodeURI()

  • decodeURI()

  • encodeURIComponent()

  • decodeURIComponent()

  • eval()

Note

The Black Box Function

Often, when you invoke functions, your program doesn't need to know how these functions work internally. You can think of a function as a black box: you give it some values (as input parameters) and then you take the output result it returns. This is true for any function—one that's built into the JavaScript engine, one that you create, or one that a co-worker or someone else created.

parseInt()

parseInt() takes any type of input (most often a string) and tries to make an integer out of it. If it fails, it returns NaN.

>>> parseInt('123')

123

>>> parseInt('abc123')

NaN

>>> parseInt('1abc23')

1

>>> parseInt('123abc')

123

The function accepts an optional second parameter, which is the radix, telling the function what type of number to expect—decimal, hexadecimal, binary, and so on. For example trying to extract a decimal number out of the string FF makes no sense, so the result is NaN, but if you try FF as a hexadecimal, then you get 255.

>>> parseInt('FF', 10)

NaN

>>> parseInt('FF', 16)

255

Another example would be parsing a string with a base 10 (decimal) and base 8 (octal).

>>> parseInt('0377', 10)

377

>>> parseInt('0377', 8)

255

If you omit the second parameter when calling parseInt(), the function will assume 10 (a decimal), with these exceptions:

  • If you pass a string beginning with 0x as a first parameter, then the second is assumed to be 16 (a hexadecimal number is assumed)

  • If the first parameter starts with 0, the function assumes 8 as a second parameter (an octal number is assumed)

>>> parseInt('377') 

377

>>> parseInt('0377') 

255

>>> parseInt('0x377') 

887

The safest thing to do is to always specify the radix. If you omit the radix, your code will probably still work in 99% of cases (because most often you parse decimals), but every once in a while it might cause you a bit of hair loss while debugging some problems. For example, imagine you have a form field that accepts calendar days and the user types 08; if you omit the radix you might get unexpected results.

parseFloat()

parseFloat() is the same as parseInt() but it also looks for decimals when trying to figure out a number from your input. This function takes only one parameter.

>>> parseFloat('123')

123

>>> parseFloat('1.23')

1.23

>>> parseFloat('1.23abc.00')

1.23

>>> parseFloat('a.bc1.23')

NaN

As with parseInt(), parseFloat() gives up at the first occurrence of an unexpected character, even though the rest of the string might have usable numbers in it.

>>> parseFloat('a123.34')

NaN

>>> parseFloat('12a3.34')

12

parseFloat() understands exponents in the input (unlike parseInt()).

>>> parseFloat('123e-2')

1.23

>>> parseFloat('123e2')

12300

>>> parseInt('1e10')

1

isNaN()

Using isNaN() you can check if an input value is a valid number that can safely be used in arithmetic operations. This function is also a convenient way to check whether parseInt() or parseFloat() succeeded.

>>> isNaN(NaN)

true

>>> isNaN(123)

false

>>> isNaN(1.23)

false

>>> isNaN(parseInt('abc123'))

true

The function will also try to convert the input to a number:

>>> isNaN('1.23')

false

>>> isNaN('a1.23')

true

The isNaN() function is useful because NaN is not equal to itself. So, surprisingly, NaN === NaN is false.

isFinite()

isFinite() checks whether the input is a number that is neither Infinity nor NaN.

>>> isFinite(Infinity)

false

>>> isFinite(-Infinity)

false

>>> isFinite(12)

true

>>> isFinite(1e308)

true

>>> isFinite(1e309)

false

If you wonder about the results returned by last two calls, remember from the previous chapter that the biggest number in JavaScript is 1.7976931348623157e+308.

Encode/Decode URIs

In a URL (Uniform Resource Locator) or a URI (Uniform Resource Identifier), some characters have special meanings. If you want to "escape" those characters, you can use the functions encodeURI() or encodeURIComponent(). The first one will return a usable URL, while the second one assumes you're only passing a part of the URL, like a query string for example, and will encode all applicable characters.

>>> var url = 'http://www.packtpub.com/scr ipt.php?q=this and that';
>>> encodeURI(url);

"http://www.packtpub.com/scr%20ipt.php?q=this%20and%20that"

>>> encodeURIComponent(url);

"http%3A%2F%2Fwww.packtpub.com%2Fscr%20ipt.php%3Fq%3Dthis%20and%20that"

The opposites of encodeURI() and encodeURIComponent() are decodeURI() and decodeURIComponent() respectively. Sometimes, in older code, you might see the similar functions escape() and unescape() but these functions have been deprecated and should not be used.

eval()

eval() takes a string input and executes it as JavaScript code:

>>> eval('var ii = 2;')
>>> ii

2

So eval('var ii = 2;') is the same as simply var ii = 2;

eval() can be useful sometimes, but should be avoided if there are other options. Most of the time there will be alternatives and, in most cases, the alternatives are more elegant and easier to write and maintain. "Eval is evil" is a mantra you can often hear from seasoned JavaScript programmers. The drawbacks of using eval() are:

  • Performance—it is slower to evaluate "live" code, than to have the code directly in the script.

  • Security—JavaScript is powerful, which also means it can cause damage. If you don't trust the source of the input you pass to eval(), just don't use it.

A Bonus—the alert() Function

Let's take a look at one very common function—alert(). This is not part of the core JavaScript (it is not in the ECMA specification), but it is provided by the host environment—the browser. It shows a string of text in a message box. It can also be useful for debugging sometimes, although the Firebug debugger is a much better tool for this purpose.

Here's a screenshot showing the result of executing the code alert("hello!")

A Bonus—the alert() Function

Before using this function, bear in mind that it blocks the browser thread, meaning that no other code will be executed until the user closes the alert. If you have a busy AJAX-type application, it is generally not a good idea to use alert().

Scope of Variables

I t is important to note, especially if you have come to JavaScript from another language, that variables in JavaScript are not defined in a block scope, but in a function scope. This means that if a variable is defined inside a function, it's not visible outside of the function. However, a variable defined inside an if or a for code block is visible outside the code block. The term "global variables" describes variables you define outside of any function, as opposed to "local variables" which are defined inside a function. The code inside a function has access to all global variables as well as to its own local variables.

In the next example:

  • The function f() has access to the variable global

  • Outside of the function f(), the variable local doesn't exist

var global = 1;
function f() {
  var local = 2;
  global++;
  return global;
}
>>> f();

2

>>> f();

3

>>> local

local is not defined

It is also important to note that if you don't use var to declare a variable, this variable is automatically assigned global scope. Let's see an example:

Scope of Variables

What happened? The function f() contains the variable local. Before calling the function, the variable doesn't exist. When you call the function for the first time, the variable local is created with a global scope. FThen if you access local outside the function, it's available.

Note

B est Practice Tips

  • Minimize the number of global variables. Imagine two people working on two different functions in the same script and they both decide to use the same name for their global variable. This could easily lead to unexpected results and hard-to-find bugs.

  • Always declare your variables with the var statement.

Here's an interesting example that shows an important aspect of the local versus global scoping.

var a = 123;
function f() { 
  alert(a);
  var a = 1;
  alert(a); 
} 
f(); 

You might expect that the first alert() will display 123 (the value of the global variable a) and the second will display 1 (the local a). This is not the case. The first alert will show "undefined". This is because inside the function the local scope is more important than the global scope. So a local variable overwrites any global variable with the same name. At the time of the first alert() a was not yet defined (hence the value undefined) but it still existed in the local space.

Functions are Data

This is an important concept that we'll need later on—functions in JavaScript are actually data. This means that the following two ways to define a function are exactly the same:

function f(){return 1;}
var f = function(){return 1;}

The second way of defining a function is known as function literal notation.

When you use the typeof operator on a variable that holds a function value, it returns the string "function".

>>> function f(){return 1;}
>>> typeof f

"function"

So JavaScript functions are data, but a special kind of data with two important features:

  • They contain code

  • They are executable (can be invoked)

As you saw before, the way to execute a function is by adding parentheses after its name. As the next example demonstrates, this works regardless of how the function was defined. In the example, you can also see how a function is treated as a normal variable—it can be copied to a different variable and even deleted.

>>> var sum = function(a, b) {return a + b;}
>>> var add = sum;
>>> delete sum

true

>>> typeof sum;

"undefined"

>>> typeof add;

"function"

>>> add(1, 2);

3

Because functions are data assigned to variables, the same rules for naming functions apply as for naming variables—a function name cannot start with a number and it can contain any combination of letters, numbers, and the underscore character.

A nonymous Functions

In JavaScript, it's ok to have pieces of data lying around your program. Imagine you have the following in your code.

>>> "test"; [1,2,3]; undefined; null; 1; 

This code may look a little odd, because it doesn't actually do anything, but the code is valid and is not going to cause an error. You can say that this code contains anonymous data—anonymous because the data pieces are not assigned to any variable and therefore don't have a name.

As you now know, functions are like any other variable so they can also be used without being assigned a name:

>>> function(a){return a;}

Now, these anonymous pieces of data scattered around your code are not really useful, except if they happen to be functions. In this case, there can be two elegant uses for them:

  • You can pass an anonymous function as a parameter to another function. The receiving function can do something useful with the function that you pass.

  • You can define an anonymous function and execute it right away.

Let's see these two applications of the anonymous functions in more detail.

C allback Functions

Because a function is just like any other data assigned to a variable, it can be defined, deleted, copied, and why not also passed as an argument to other functions?

Here's an example of a function that accepts two functions as parameters, executes them, and returns the sum of what each of them returns.

function invoke_and_add(a, b){
  return a() + b();
}

Now let's define two simple additional functions that only return hardcoded values:

function one() {
  return 1;
}
function two() {
  return 2;
}

Now we can pass those functions to the original function add() and get the result:

>>> invoke_and_add(one, two);

3

Another example of passing a function as a parameter is to use anonymous functions. Instead of defining one() and two(), you can simply do:

invoke_and_add(function(){return 1;}, function(){return 2;})

When you pass a function A to another function B and B executes A, it's often said that A is a callback function. If A doesn't have a name, then you can say that it's an anonymous callback function.

When are the callback functions useful? Let's see some examples that demonstrate the benefits of the callback functions, namely:

  • They let you pass functions without the need to name them (which means there are less global variables)

  • You can delegate the responsibility of calling a function to another function (which means there is less code to write)

  • They can help with performance

C allback Examples

Take a look at this common scenario: you have a function that returns a value, which you then pass to another function. In our example, the first function, multiplyByTwo(), accepts three parameters, loops through them, multiplying them by two and returns an array containing the result. The second function, addOne(), takes a value, adds one to it and returns it.

function multiplyByTwo(a, b, c) {
  var i, ar = []; 
  for(i = 0; i < 3; i++) {
    ar[i] = arguments[i] * 2;
  } 
  return ar;
}
function addOne(a) {
  return a + 1;
}

Testing the functions we have so far:

>>> multiplyByTwo(1, 2, 3);

[2, 4, 6]

>>> addOne(100)

101

Now let's say we want to have an array myarr that contains three elements, and each of the elements is to be passed through both functions. First, let's start with a call to multiplyByTwo().

>>> var myarr = [];
>>> myarr = multiplyByTwo(10, 20, 30);

[20, 40, 60]

Now loop through each element, passing it to addOne().

>>> for (var i = 0; i < 3; i++) {myarr[i] = addOne(myarr[i]);}
>>> myarr

[21, 41, 61]

As you see everything works fine, but there's still room for improvement. One thing is that there were two loops. Loops can be expensive if they go through a lot or repetitions. We can achieve the result we want with one loop only. Here's how to modify multiplyByTwo() so that it accepts a callback function and invokes callback on every iteration:

function multiplyByTwo(a, b, c, callback) {
  var i, ar = []; 
  for(i = 0; i < 3; i++) {
    ar[i] = callback(arguments[i] * 2);
  } 
  return ar;
}

By using the modified function, the whole work is now done with just one function call, which passes the start values and the callback.

>>> myarr = multiplyByTwo(1, 2, 3, addOne);

[3, 5, 7]

Instead of defining addOne() we can use an anonymous function, this way saving an extra global variable.

>>> myarr = multiplyByTwo(1, 2, 3, function(a){return a + 1});

[3, 5, 7]

Anonymous functions are easy to change should the need arise:

>>> myarr = multiplyByTwo(1, 2, 3, function(a){return a + 2});

[4, 6, 8]

Self-invoking Functions

So far we have discussed using anonymous functions as callbacks. Let's see another application of an anonymous function—calling this function right after it was defined. Here's an example:

(
  function(){
    alert('boo'),
  }
)()

The syntax may look a little scary at first, but it's actually easy—you simply place an anonymous function definition inside parentheses followed by another set of parentheses. The second set basically says "execute now" and is also the place to put any parameters that your anonymous function might accept.

(
  function(name){
    alert('Hello ' + name + '!'),
  }
)('dude')

One good reason for using self-invoking anonymous functions is to have some work done without creating global variables. A drawback, of course, is that you cannot execute the same function twice (unless you put it inside a loop or another function). This makes the anonymous self-invoking functions best suited for one-off or initialization tasks.

Inner (Private) Functions

Bearing in mind that a function is just like any other value, there's nothing that stops you from defining a function inside another function.

function a(param) {
  function b(theinput) {
    return theinput * 2;
  };
  return 'The result is ' + b(param);
};

Using the function literal notation, this can also be written as:

var a = function(param) {
  var b = function(theinput) {
    return theinput * 2;
  };
  return 'The result is ' + b(param);
};

When you call the global function a(), it will internally call the local function b(). Since b() is local, it's not accessible outside a(), so we can say it's a private function.

>>> a(2);

"The result is 4"

>>> a(8);

"The result is 16"

>>> b(2);

b is not defined

The benefit of using private functions are as follows:

  • You keep the global namespace clean (smaller chance of naming collisions).

  • Privacy—you expose only the functions you decide to the "outside world", keeping to yourself functionality that is not meant to be consumed by the rest of the application.

Functions that Return Functions

As mentioned earlier, a function always returns a value, and if it doesn't do it explicitly with return, then it it does so implicitly by returning undefined. A function can return only one value and this value could just as easily be another function.

function a() {
  alert('A!'), 
  return function(){
    alert('B!'), 
  };
}

In this example the function a() does its job (says A!) and returns another function that does something else (says B!). You can assign the return value to a variable and then use this variable as a normal function:

>>> var newFunc = a(); 
>>> newFunc(); 

Here the first line will alert A! and the second will alert B!.

If you want to execute the returned function immediately, without assigning it to a new variable, you can simply use another set of parentheses. The end result will be the same.

>>> a()(); 

Function, Rewrite Thyself!

Because a function can return a function, you can use the new function to replace the old one. Continuing with the previous example, you can take the value returned by the call to a() to overwrite the actual a() function:

>>> a = a();

The above alerts A!, but the next time you call a() it alerts B!.

This is useful when a function has some initial one-off work to do. The function overwrites itself after the first call in order to avoid doing unnecessary repetitive work every time it's called.

In the example above, we redefined the function from the outside—we got the returned value and assigned it back to the function. But the function can actually rewrite itself from the inside.

function a() {
  alert('A!'),
  a = function(){
    alert('B!'),
  };
}

If you call this function for the first time, it will:

  • Alert A! (consider this as being the one-off preparatory work)

  • Redefine the global variable a, assigning a new function to it

Every subsequent time that the function is called, it will alert B!

Here's another example that combines several of the techniques discussed in the last few sections of this chapter:

var a = function() {
  function someSetup(){
    var setup = 'done';
  }
  function actualWork() {
    alert('Worky-worky'),
  }
  someSetup();
  return actualWork;
}();

In this example:

  • You have private functions—someSetup() and actualWork().

  • You have a self-invoking function—the function a() calls itself using the parentheses following its definition.

  • The function executes for the first time, calls someSetup() and then returns a reference to the variable actualWork, which is a function. Notice that there are no parentheses in the return, because it is returning a function reference, not the result of invoking this function.

  • Because the whole thing starts with var a =..., the value returned by the self-invoked function is assigned to a.

If you want to test your understanding of the topics just discussed, answer the following questions. What will the code above alert when:

  • It is initially loaded?

  • You call a() afterwards?

These techniques could be really useful when working in the browser environment. Because different browsers can have different ways of achieving the same thing and you know that the browser features don't change between function calls, you can have a function determine the best way to do the work in the current browser, then redefine itself, so that the "browser feature sniffing" is done only once. You'll see concrete examples of this scenario later in this book.

Closures

The rest of the chapter is about closures (what better way to close a chapter?). Closures could be a little hard to grasp initially, so don't feel discouraged if you don't "get it" during the first read. You should go through the rest of the chapter and experiment with the examples on you own, but if you feel you don't fully understand the concept, you can come back to it later when the topics discussed previously in this chapter have had a chance to sink in.

Before we get to closures, let's review and expand on the concept of scope in JavaScript.

Scope Chain

As you know, in JavaScript, unlike many other languages, there is no curly braces scope, but there is function scope. A variable defined in a function is not visible outside the function, but a variable defined in a code block (an if or a for loop) is visible outside the block.

>>> var a = 1; function f(){var b = 1; return a;}
>>> f();

1

>>> b

b is not defined

The variable a is in the global space, whereas b is in the scope of the function f(). So:

  • Inside f(), both a and b are visible

  • Outside f(), a is visible, but b is not

If you define a function n() nested inside f(), n() will have access to variables in its own scope, plus the scope of its "parents". This is known as scope chain, and the chain can be as long (deep) as you need it to be.

var a = 1; 
function f(){
  var b = 1; 
  function n() {
    var c = 3;
  }
}

Lexical Scope

In JavaScript, functions have lexical scope. This means that functions create their environment (scope) when they are defined, not when they are executed. Let's see an example:

>>> function f1(){var a = 1; f2();}
>>> function f2(){return a;}
>>> f1();

a is not defined

Inside the function f1() we call the function f2(). Because the local variable a is also inside f1(), one might expect that f2() will have access to a, but that's not the case. At the time when f2() was defined (as opposed to executed), there was no a in sight. f2(), just like f1(), only has access to its own scope and the global scope. f1() and f2() don't share their local scopes.

When a function is defined, it "remembers" its environment, its scope chain. This doesn't mean that the function also remembers every single variable that is in scope. Just the opposite—you can add, remove or update variables inside the scope of the function and the function will see the latest, up-to-date state of the variables. If you continue with the example above and declare a global variable a, f2() will see it, because f2() knows the path to the global environment and can access everything in that environment. Also notice how f1() includes a call to f2(), and it works- even though although f2() is not yet defined. All f1() needs to know is its scope, so that everything that shows up in scope becomes immediately available to f1().

>>> function f1(){var a = 1; return f2();}
>>> function f2(){return a;}
>>> f1();

a is not defined

>>> var a = 5;
>>> f1();

5

>>> a = 55;
>>> f1();

55

>>> delete a;

true

>>> f1();

a is not defined

This behavior gives JavaScript great flexibility—you can add and remove variables and add them again, and it's totally fine. You can keep experimenting and delete the function f2(), then redefine it again with a different body. In the end, f1() will still work, because all it needs to know is how to access its scope and not what this scope used to contain at some point in time. Continuing with the example above:

>>> delete f2;

true

>>> f1()

f2 is not defined

>>> var f2 = function(){return a * 2;}
>>> var a = 5;

5

>>> f1();

10

Breaking the Chain with a Closure

Let's introduce closures with an illustration.

There is the global scope. Think of it as the Universe, as if it contains everything.

Breaking the Chain with a Closure

It can contain variables such as a and functions such as F.

Breaking the Chain with a Closure

Functions have their own private space and can use it to store other variables (and functions). At some point, you end up with a picture like this:

Breaking the Chain with a Closure

If you're at point a, you're inside the global space. If you're at point b, which is inside the space of the function F, then you have access to the global space and to the F-space. If you're at point c, which is inside the function N, then you can access the global space, the F-space and the N-space You cannot reach from a to b, because b is invisible outside F. But you can get from c to b if you want, or from N to b. The interesting thing—the closure—happens when somehow N breaks out of F and ends up in the global space.

Breaking the Chain with a Closure

What happens then? N is in the same global space as a. And since functions remember the environment in which they were defined, N will still have access to the F-space, and hence can access b. This is interesting, because N is where a is and yet N does have access to b, but a doesn't.

And how does N break the chain? By making itself global (omitting var) or by having F deliver (or return) it to the global space. Let's see how this is done in practice.

Closure #1

Take a look at this function:

function f(){
  var b = "b"; 
  return function(){
    return b;
  }
}

This function contains a variable b, which is local, and therefore inaccessible from the global space:

>>> b

b is not defined

Check out the return value of f(): it's another function. Think of it as N in the illustrations above. This new function has access to its private space, to f()'s space and to the global space. So it can see b. Because f() is callable from the global space (it's a global function), you can call it and assign the returned value to another global variable. The result—a new global function that has access to f()'s private space.

>>> var n = f();
>>> n();

"b"

Closure #2

The final result of the next example will be the same as the previous example, but the way to achieve it is a little different. f() doesn't return a function, but instead it creates a new global function n() inside its body.

Let's start by declaring a placeholder for the global function-to-be. This is optional, but it's always good to declare your variables. Then you can define the function f() like this:

var n;
function f(){
  var b = "b"; 
  n = function(){
    return b;
  }
}

Now what happens if you invoke f()?

>>> f();

A new function is defined inside f() and since it's missing the var statement, it becomes global. During definition time, n() was inside f(), so it had access to f()'s scope. n() will keep its access to f()'s scope, even though n() becomes part of the global space.

>>> n();

"b"

A Definition and Closure #3

Based on what we have discussed so far, you can say that a closure is created when a function keeps a link to its parent's scope even after the parent has returned.

When you pass an argument to a function it becomes available as a local variable. You can create a function that returns another function, which in turn returns its parent's argument.

function f(arg) {
  var n = function(){
    return arg;
  }; 
  arg++; 
  return n;
}

You use the function like this:

>>> var m = f(123);
>>> m();

124

Notice how arg++ was incremented after the function was defined and yet, when called, m() returned the updated value. This demonstrates how the function binds to its scope, not to the current variables and their values found in the scope.

Closures in a Loop

Here's something that can easily lead to hard-to-spot bugs, because, on the surface, everything looks normal.

Let's loop three times, each time creating a new function that returns the loop sequence number. The new functions will be added to an array and we'll return the array at the end. Here's the function:

function f() {
  var a = [];
  var i; 
  for(i = 0; i < 3; i++) {
    a[i] = function(){
      return i;
    }
  } 
  return a;
}

Let's run the function, assigning the result to the array a.

>>> var a = f();

Now you have an array of three functions. Let's invoke them by adding parentheses after each array element. The expected behavior is to see the loop sequence printed out: 0, 1, and 2. Let's try:

>>> a[0]()

3

>>> a[1]()

3

>>> a[2]()

3

Hmm, not quite what we expected. What happened here? We created three closures that all point to the same local variable i. Closures don't remember the value, they only link (reference) the i variable and will return its current value. After the loop, i's value is 3. So all the three functions point to the same value.

(Why 3 and not 2 is another good question to think about, for better understanding the for loop.)

So how do you implement the correct behavior? You need three different variables. An elegant solution is to use another closure:

function f() {
  var a = [];
  var i;
  for(i = 0; i < 3; i++) {
    a[i] = (function(x){
      return function(){
        return x;
      }
    })(i);
  }
  return a;
}

This gives the expected result:

>>> var a = f(); 
>>> a[0](); 

0

>>> a[1](); 

1

>>> a[2](); 

2

Here, instead of just creating a function that returns i, you pass i to another self-executing function. For this function, i becomes the local value x, and x has a different value every time.

Alternatively, you can use a "normal" (as opposed to self-invoking) inner function to achieve the same result. The key is to use the middle function to "localize" the value of i at every iteration.

function f() {
  function makeClosure(x) {
    return function(){
      return x;
    }
  }
  var a = [];
  var i;
  for(i = 0; i < 3; i++) {
    a[i] = makeClosure(i);
  }
  return a;
}

Getter/Setter

Let' s see two more examples of using closures. The first one involves the creation of getter and setter functions. Imagine you have a variable that will contain a very specific range of values. You don't want to expose this variable because you don't want just any part of the code to be able to alter its value. You protect this variable inside a function and provide two additional functions—one to get the value and one to set it. The one that sets it could contain some logic to validate a value before assigning it to the protected variable (let's skip the validation part for the sake of keeping the example simple).

You place both the getter and the setter functions inside the same function that contains the secret variable, so that they share the same scope:

var getValue, setValue; 
(function() {
  var secret = 0; 
  getValue = function(){
    return secret;
  };
  setValue = function(v){
    secret = v;
  }; 
})()

In this case, the function that contains everything is a self-invoking anonymous function. It defines setValue() and getValue() as global functions, while the secret variable remains local and inaccessible directly.

>>> getValue()

0

>>> setValue(123)
>>> getValue()

123

Iterator

The last closure example (also the last example in the chapter) shows the use of a closure to accomplish Iterator functionality.

You already know how to loop through a simple array, but there might be cases where you have a more complicated data structure with different rules as to what the sequence of values is. You can wrap the complicated "who's next" logic into an easy-to-use next() function. Then you simply call next() every time you need the consecutive value. For this example, we'll just use a simple array, and not a complex data structure.

Here's an initialization function that takes an input array and also defines a private pointer i that will always point to the next element in the array.

function setup(x) {
  var i = 0; 
  return function(){
    return x[i++];
  };
}

Calling the setup() function with a data array will create the next() function for you.

>>> var next = setup(['a', 'b', 'c']);

From there it's easy and fun: calling the same function over and over again gives you the next element.

>>> next();

"a"

>>> next();

"b"

>>> next();

"c"

Summary

You have now completed the introduction to the fundamental concepts related to functions in JavaScript. You've been laying the groundwork that will allow you to quickly grasp the concepts of object-oriented JavaScript and the patterns that are in use in modern JavaScript programming. So far, we've been avoiding the OO features, but as you have reached this point in the book, it is only going to get more interesting from here on in. Let's take a moment and review the topics discussed in this chapter:

  • The basics of how to define and invoke (call) a function

  • Function parameters and their flexibility

  • Built-in functions—parseInt(), parseFloat(), isNaN(), isFinite(), eval(), and the four functions to encode/decode a URL

  • The scope of variables in JavaScript—no curly braces scope, variables have only function scope; functions have lexical scope and follow a scope chain

  • Functions as data—a function is like any other piece of data that you assign to a variable and a lot of interesting applications follow from this, such as:

    • Private functions and private variables

    • Anonymous functions

    • Callbacks

    • Self-invoking functions

    • Functions overwriting themselves

  • Closures

Exercises

  1. Write a function that converts a hexadecimal color, for example blue "#0000FF", into its RGB representation "rgb(0, 0, 255)". Name your function getRGB() and test it with this code:

    	>>> var a = getRGB("#00FF00"); 
    	>>> a; 

    "rgb(0, 255, 0)"

  2. What does each of these lines print in the console?

    	>>> parseInt(1e1)
    	>>> parseInt('1e1')
    	>>> parseFloat('1e1')
    	>>> isFinite(0/10)
    	>>> isFinite(20/0)
    	>>> isNaN(parseInt(NaN));
  3. What does this following code alert()?

    	var a = 1;
    	function f() {
    	  var a = 2;
    	  function n() {
    	    alert(a);
    	  }
    	  n();
    	}
    	f(); 
  4. All these examples alert "Boo!". Can you explain why?

    4.1.

    	   var f = alert;
    	   eval('f("Boo!")'),

    4.2.

    	   var e;
    	   var f = alert;
    	   eval('e=f')('Boo!'), 

    4.3.

    	   (
    	    function(){
      	      return alert;
    	    }
    	  )()('Boo!'),
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.227.231