Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Learning jQuery - Fourth Edition

You're reading from   Learning jQuery - Fourth Edition Add to your current website development skills with this brilliant guide to JQuery. This step by step course needs little prior JavaScript knowledge so is suitable for beginners and more seasoned developers alike.

Arrow left icon
Product type Paperback
Published in Jun 2013
Publisher Packt
ISBN-13 9781782163145
Length 444 pages
Edition 4th Edition
Languages
Tools
Arrow right icon
Toc

Table of Contents (24) Chapters Close

Learning jQuery Fourth Edition
Credits
Foreword
About the Authors
About the Reviewers
www.PacktPub.com
Preface
1. Getting Started FREE CHAPTER 2. Selecting Elements 3. Handling Events 4. Styling and Animating 5. Manipulating the DOM 6. Sending Data with Ajax 7. Using Plugins 8. Developing Plugins 9. Advanced Selectors and Traversing 10. Advanced Events 11. Advanced Effects 12. Advanced DOM Manipulation 13. Advanced Ajax JavaScript Closures Testing JavaScript with QUnit Quick Reference Index

Creating closures in jQuery


The methods we have seen throughout the jQuery library often take at least one function as a parameter. For convenience, we often use anonymous functions so that we can define the function behavior right when it is needed. This means that functions are rarely in the top-level namespace; they are usually inner functions, which means they can quite easily create closures.

Passing arguments to $(document).ready()

Nearly all the code we write using jQuery ends up getting placed inside a function passed as an argument to $(document).ready(). We do this to guarantee that the DOM has loaded before the code is run, which is usually a requirement for interesting jQuery code. When a function is created and passed to .ready(), a reference to the function is stored as part of the global jQuery object. This reference is then called at a later time when the DOM is ready.

We usually place the $(document).ready() construct at the top level of the code structure, so this function is not really part of a closure. However, since our code is usually written inside this function, everything else is an inner function:

$(document).ready(function() {
  var readyVar = 0;
  function innerFn() {
    readyVar++;
    console.log('readyVar = ' + readyVar);
  }
  innerFn();
  innerFn();
});

Listing A.10

This looks like many of our earlier examples, except that in this case the outer function is the callback passed to $(document).ready(). Since innerFn() is defined inside of it and refers to readyVar which is in the scope of the callback function, innerFn() and its environment create a closure. We can see this by noting that the value of readyVar persists between calls to the function:

readyVar = 1
readyVar = 2

The fact that most jQuery code is inside a function body is useful, because this can protect against some namespace collisions. For example, it is this feature that allows us to use jQuery.noConflict() to free up the $ shortcut for other libraries while still being able to define the shortcut locally for use within our .ready() handler.

Assigning event handlers

The $(document).ready() construct usually wraps the rest of our code, including the assignment of event handlers. Since handlers are functions, they become inner functions. Since those inner functions are stored and called later, they can create closures.

A simple click handler can illustrate this:

$(document).ready(function() {
  var counter = 0;
  $('#button-1').click(function(event) {
    event.preventDefault();
    counter++;
    console.log('counter = ' + counter);
  });
});

Listing A.11

Because the variable counter is declared inside of the .ready() handler, it is only available to the jQuery code inside this block and not to outside code. It can be referenced by the code in the click handler, however, which increments and displays the variable's value. Because a closure is created, the same instance of counter is referenced each time the link is clicked. This means that the messages display a continuously incrementing set of values, not just 1 each time:

counter = 1
counter = 2
counter = 3

Event handlers can share their closing environments, just like other functions can:

$(document).ready(function() {
  var counter = 0;
  $('#button-1').click(function(event) {
    event.preventDefault();
    counter++;
    console.log('counter = ' + counter);
  });
  $('#button-2').click(function(event) {
    event.preventDefault();
    counter--;
    console.log('counter = ' + counter);
  });
});

Listing A.12

Since both the functions reference the same counter variable, the incrementing and decrementing operations of the two links affect the same value rather than being independent:

counter = 1
counter = 2
counter = 1
counter = 0

Binding handlers in loops

Looping constructs can pose interesting challenges due to the way closures operate. Consider a scenario in which we create elements in a loop and bind behaviors to those elements based on the loop's index:

$(document).ready(function() {
  for (var i = 0; i < 5; i++) {
    $('<div>Print ' + i + '</div>')
      .click(function() {
        console.log(i);
      }).insertBefore('#results');
  }
});

Listing A.13

The variable i is set to the numbers 0 through 4 in turn and a new <div> element is created each time. The elements each have a unique text label as we would expect:

Print 0
Print 1
Print 2
Print 3
Print 4

We might anticipate that clicking on an item would cause the corresponding number to be logged to the console. However, the click handlers always print the number 5 instead. Each click handler's reference to i is the same, even though the value of i is different at the time the handler is bound. The variable is the same one and so the final value of i (5) is fetched when the click actually happens.

We can get around this problem in a number of ways. First, we could replace the for loop with the jQuery $.each() function:

$(document).ready(function() {
  $.each([0, 1, 2, 3, 4], function(index, value) {
    $('<div>Print ' + value + '</div>')
      .click(function() {
        console.log(value);
      }).insertBefore('#results');
  });
});

Listing A.14

Function parameters are like variables defined within functions: the variable value is actually a different variable each time through the loop. Because of this, each click handler is pointing to a different value variable, which in turn means that click handlers on the elements print numbers corresponding to the element labels.

We can also exploit the same properties of function parameters to solve this problem without calling $.each(). Inside the for loop, we can define and execute a new function that takes care of separating the values of i apart into distinct variables:

$(document).ready(function() {
  for (var i = 0; i < 5; i++) {
    (function(value) {
      $('<div>Print ' + value + '</div>')
        .click(function() {
          console.log(value);
        }).insertBefore('#results');
    })(i);
  }
});

Listing A.15

In Chapter 8, Developing Plugins, we were introduced to this construct, called an immediately invoked function expression (IIFE), as a means of redefining the $ alias for the jQuery object after $.noConflict() has been called. Here, we use it to pass in i as a parameter named value that is distinct for each click handler.

Finally, we can use a feature of the jQuery event system to solve the problem a different way. The .on() method accepts an object parameter that is passed along to the event handler as event.data:

$(document).ready(function() {
  for (var i = 0; i < 5; i++) {
    $('<div>Print ' + i + '</div>')
      .on('click', {value: i}, function(event) {
        console.log(event.data.value);
      }).insertBefore('#results');
  }
});

Listing A.16

In this case, i is provided as data to the .on() method and can be retrieved inside the handler by inspecting event.data.value. Once again, since event is a function parameter, it is a unique entity each time a handler is invoked.

Giving names to functions

These examples have used anonymous functions, as has been our custom in jQuery code. This makes no difference in the construction of closures; closures can come from named or anonymous functions. For example, we can write an anonymous function to report the index of an <input> button within a jQuery object:

$(document).ready(function() {
  $('input').each(function(index) {
    $(this).click(function(event) {
      event.preventDefault();
      console.log('index = ' + index);
    });
  });
});

Listing A.17

Because the innermost function is defined within the .each() callback, this code actually creates as many functions as there are buttons. Each of these functions is attached as a click handler to one of the buttons. The functions have index in their closing environment, since it is a parameter to the .each() callback. This behaves the same way as if the click handler were written as a named function:

$(document).ready(function() {
  $('input').each(function(index) {
    function clickHandler(event) {
      event.preventDefault();
      console.log('index = ' + index);
    }

    $(this).click(clickHandler);
  });
});

Listing A.18

The version with the anonymous function is just a bit shorter. The position of this named function is still relevant. The following code will trigger a JavaScript error whenever a button is clicked:

$(document).ready(function() {
  function clickHandler(event) {
    event.preventDefault();
    console.log('index = ' + index);
  }

  $('input').each(function(index) {
    $(this).click(clickHandler);
  });
});

Listing A.19

The error is triggered because index is not found in the closing environment of clickHandler(). It remains a free variable, and so is undefined in this context.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime