Book Image

Learning jQuery - Fourth Edition - Fourth Edition

Book Image

Learning jQuery - Fourth Edition - Fourth Edition

Overview of this book

To build interesting, interactive sites, developers are turning to JavaScript libraries such as jQuery to automate common tasks and simplify complicated ones. Because many web developers have more experience with HTML and CSS than with JavaScript, the library's design lends itself to a quick start for designers with little programming experience. Experienced programmers will also be aided by its conceptual consistency. LearningjQuery - Fourth Edition is revised and updated version of jQuery. You will learn the basics of jQuery for adding interactions and animations to your pages. Even if previous attempts at writing JavaScript have left you baffled, this book will guide you past the pitfalls associated with AJAX, events, effects, and advanced JavaScript language features. Starting with an introduction to jQuery, you will first be shown how to write a functioning jQuery program in just three lines of code. Learn how to add impact to your actions through a set of simple visual effects and to create, copy, reassemble, and embellish content using jQuery's DOM modification methods. The book will take you through many detailed, real-world examples, and even equip you to extend the jQuery library itself with your own plug-ins.
Table of Contents (24 chapters)
Learning jQuery Fourth Edition
Credits
Foreword
About the Authors
About the Reviewers
www.PacktPub.com
Preface
Index

Creating inner functions


JavaScript is fortunate to number itself among the programming languages that support inner function declarations. Many traditional programming languages, such as C, collect all functions in a single top-level scope. Languages with inner functions, on the other hand, allow us to gather small utility functions where they are needed, avoiding namespace pollution.

An inner function is simply a function that is defined inside of another function. For example:

function outerFn() {
  function innerFn() {
  }
}

Listing A.1

Here, innerFn() is an inner function, contained within the scope of outerFn(). This means that a call to innerFn() is valid within outerFn(), but not outside of it. The following code results in a JavaScript error:

function outerFn() {
  console.log('Outer function');
  function innerFn() {
    console.log('Inner Function');
  }
}
console.log('innerFn():');
innerFn();

Listing A.2

We can successfully run the code, though, by calling innerFn() from within outerFn():

function outerFn() {
  console.log('Outer function');
  function innerFn() {
    console.log('Inner function');
  }
  innerFn();
}
console.log('outerFn():');
outerFn();

Listing A.3

This results in the following output:

outerFn():
Outer function
Inner function

This technique is especially handy for small, single-purpose functions. For example, algorithms that are recursive, but have a nonrecursive API wrapper, are often best expressed with an inner function as a helper.

Calling inner functions from elsewhere

The plot thickens when function references come into play. Some languages, such as Pascal, allow the use of inner functions for the purpose of code hiding only; those functions are forever entombed within their parent functions. JavaScript, on the other hand, allows us to pass functions around just as if they were any other kind of data. This means inner functions can escape their captors.

The escape route can wind in many different directions. For example, suppose the function is assigned to a global variable:

var globalVar;

function outerFn() {
  console.log('Outer function');
  function innerFn() {
    console.log('Inner function');
  }
  globalVar = innerFn;
}
console.log('outerFn():');
outerFn();
console.log('globalVar():');
globalVar();

Listing A.4

The call to outerFn() after the function definition modifies the global variable globalVar. It is now a reference to innerFn(). This means that the later call to globalVar() operates just as an inner call to innerFn() would, and the print statements are reached:

outerFn():
Outer function
globalVar():
Inner function

Note that a call to innerFn() from outside of outerFn() still results in an error. Though the function has escaped by way of the reference stored in the global variable, the function name is still trapped inside the scope of outerFn().

A function reference can also find its way out of a parent function through a return value:

function outerFn() {
  console.log('Outer function');
  function innerFn() {
    console.log('Inner function');
  }
  return innerFn;
}
console.log('var fnRef = outerFn():');
var fnRef = outerFn();
console.log('fnRef():');
fnRef();

Listing A.5

Here, there is no global variable modified inside outerFn(). Instead, outerFn() returns a reference to innerFn(). The call to outerFn() results in this reference, which is stored and called itself in turn, triggering the message again:

var fnRef = outerFn():
Outer function
fnRef():
Inner function

The fact that inner functions can be invoked through a reference even after the function has gone out of scope means that JavaScript needs to keep referenced functions available as long as they could possibly be called. Each variable that refers to the function is tracked by the JavaScript runtime, and once the last has gone away, the JavaScript garbage collector comes along and frees up that bit of memory.

Understanding variable scope

Inner functions can of course have their own variables, which are restricted in scope to the function itself:

function outerFn() {
  function innerFn() {
    var innerVar = 0;
    innerVar++;
    console.log('innerVar = ' + innerVar);
  }
  return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();

Listing A.6

Each time this inner function is called, through a reference or otherwise, a new variable innerVar is created, incremented, and displayed:

innerVar = 1
innerVar = 1
innerVar = 1
innerVar = 1

Inner functions can reference global variables in the same way as any other function can:

var globalVar = 0;
function outerFn() {
  function innerFn() {
    globalVar++;
    console.log('globalVar = ' + globalVar);
  }
  return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();

Listing A.7

Now our function will consistently increment the variable with each call:

globalVar = 1
globalVar = 2
globalVar = 3
globalVar = 4

But what if the variable is local to the parent function? Since the inner function inherits its parent's scope, this variable can be referenced too:

function outerFn() {
  var outerVar = 0;
  function innerFn() {
    outerVar++;
    console.log('outerVar = ' + outerVar);
  }
  return innerFn;
}
var fnRef = outerFn();
fnRef();
fnRef();
var fnRef2 = outerFn();
fnRef2();
fnRef2();

Listing A.8

Now our function calls have more interesting behavior:

outerVar = 1
outerVar = 2
outerVar = 1
outerVar = 2

This time we get a mix of the two earlier effects. The calls to innerFn() through each reference increment outerVar independently. Note that the second call to outerFn() is not resetting the value of outerVar, but rather creating a new instance of outerVar bound to the scope of the second function call. The upshot of this is that after the previous calls, another call to fnRef() will print the value 3, and a subsequent call to fnRef2() will also print 3. The two counters are completely separate.

When a reference to an inner function finds its way outside of the scope in which the function was defined, this creates a closure on that function. We call variables that are neither parameters nor local to the inner function's free variables, and the environment of the outer function call closes them. Essentially, the fact that the function refers to a local variable in the outer function grants the variable a stay of execution. The memory is not released when the function completes, as it is still needed by the closure.