views:

82

answers:

3

I have some working Javascript that manipulates the some DOM elements. The problem is, I don't understand why it works, which is never a good thing. I am trying to learn more about object oriented javascript and javascript best practices, so the organization may seems a little strange.

Basically, I wrap two methods that manipulate the DOM inside a CSContent object. I create an instance of that object, content in $(document).ready and bind some events to the functions in content. However, I am confused as to how these functions can still be called after $(document).ready exits. Doesn't that mean that content has gone out of scope, and its functions are not available? Anyway, here is the code:

function CSContent() {
    var tweetTextArea = document.getElementById('cscontent-tweet'),
        tweetTextElement = document.getElementById('edit-cscontent-cs-content-tweet'),
        charCountElement = document.getElementById('cscontent-tweet-charactercount');

    this.toggleTweetTextarea = function () {
      $(tweetTextArea).slideToggle();
    };

    this.updateTweetCharacterCount = function () {
        var numOfCharsLeft = 140 - tweetTextElement.value.length;
        if (numOfCharsLeft < 0) {
            $(charCountElement).addClass('cscontent-negative-chars-left');
        }
        else {
            $(charCountElement).removeClass('cscontent-negative-chars-left');
        }
        charCountElement.innerHTML = '' + numOfCharsLeft + ' characters left.';
    };
}



$(document).ready(function () {
    var content = new CSContent();
    //If the twitter box starts out unchecked, then hide the text area
    if ($('#edit-cscontent-cs-content-twitter:checked').val() === undefined) {
        $('#cscontent-tweet').hide();
    }

    $('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);
    //Seems wasteful, but we bind to keyup and keypress to fix some weird miscounting behavior when deleting characters.
    $('#edit-cscontent-cs-content-tweet').keypress(content.updateTweetCharacterCount);
    $('#edit-cscontent-cs-content-tweet').keyup(content.updateTweetCharacterCount);
    content.updateTweetCharacterCount();
});
+5  A: 

This, m'lord, is called a closure: the local variable content will remain in memory after $(document).ready exits. This is also a known cause of memory leaks.

In short, you bind this function to an event listener of a DOM element and then the JavaScript garbage collector knows that it should keep the local variable intact. You can't call it directly (outside of the function), unless the event is triggered. With some, you can do this ‘manually’, if you really want to call the function afterward (e.g., using element.click() to simulate a click).

Marcel Korpel
But other than that, they are great! :) Some additional reading: https://developer.mozilla.org/en/JavaScript/Guide/Closures
Pekka
So it can access to the local variables declared in the CSContent constructor because it is a closure, and it doesn't go out of scope because it is bound to an event listener? Or does the whole closure get copied somehow to the event listener?
Jergason
@Jergason: the first.
Marcel Korpel
+1  A: 

My brain is off today, but shouldn't you be using closures in this situation?

$('#edit-cscontent-cs-content-twitter').change( 
    function(){ 
        content.toggleTweetTextarea(); 
    } 
);
epascarello
Today....? :) Hey Eric did you ever see this comment: http://stackoverflow.com/questions/3274044/best-book-on-ajax/3306936#3306936
Josh Stodola
Hmm, good idea, closures. But the OP is already using one… :P
Marcel Korpel
+2  A: 

I assume you wonder why the event handlers like

$('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);

work?

Well you don't pass content as event handler but the function that is contained in content.toggleTweetTextarea. And this reference will still exist after content does not exist anymore. There is nothing special about it. You just assigned an object (the function) to another variable. As long as at least one reference to an object exists, the object won't be garbage collected.

Now you may ask why those functions have still access to e.g. tweetTextArea ? This is indeed a closure. When the functions are created via new CSContent(), the activation context of this function is added to the scope chain of the inner functions CSContent.toggleTweetTextarea and CSContent.updateTweetCharacterCount. So even if you don't have a reference to content anymore, the scope of this function is still contained in the scope chain of the other functions.

You won't be able to access the object contained in content anymore after ready() is finished, this indeed goes out of scope.

Felix Kling