Wrapping The Window Object In A jQuery Wrapper

Posted February 2, 2010 at 8:21 PM by Ben Nadel

Tags: Javascript / DHTML

This morning, when I was building my Flickr-style photo tagging demo using jQuery, I got totally stumped trying to debug a variable problem. From what it looked like, the Window object appeared to be undefined within the context of my document-ready event handler. After picking apart the code and commenting out bits of it for like 10-15 minutes, I finally figured out what the heck was going on. It boiled down to me simply forgetting how variables get "compiled" in Javascript. I think it's something that's very easy to forget, so I figured I would share my error.

Let's walk through a really simple scenario. In the following demo, I try to create a function-local jQuery-ized variable to override the raw window object reference:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  •  
  • // Overwrite the local window reference by create a
  • // local jQuery "window" wrapper for access to things
  • // like width() and scrollTop().
  • var window = $( window );
  •  
  • // Log window.
  • console.log( window );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

As you can see, I am creating a local variable, window, which is a jQuery wrapper containing the global window object. The intent here was to create a locally-scoped window variable that would give me immediate access to jQuery methods like width(), height(), scrollLeft(), and scrollTop(). At first glance, this seems reasonable; but, when we run the above code, this the console output that we get:

[ ]

It's a jQuery wrapper alright, but the collection is empty.

When I saw this, my first thought was that the global window object was undefined in the context of the document-ready event handler. To test this, I tried putting a console log() method call before and after the variable assignment:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  •  
  • // Log the window.
  • console.log( "Before: ", window );
  •  
  • // Overwrite the local window reference by create a
  • // local jQuery "window" wrapper for access to things
  • // like width() and scrollTop().
  • var window = $( window );
  •  
  • // Log window.
  • console.log( "After: ", window );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

Here, you can see that I am logging the global window object, then creating the local window reference (using the window object), then logging the newly created window reference. When we run the above code, we get the following console output:

Before: undefined
After: [ ]

Ah-ha!! The window object is undefined in the context of the document-ready event handler! It doesn't even exist before I've tried to do anything with it. When I saw this, I figured something really crazy must be going on.

After staring at this code for like 5 minutes, I had another "ah-ha" moment. What I realized was that the above behavior was exactly what was to be expected. The problem was that I had become so accustomed to thinking about Javascript in a top-down manner, that I forgot that it actually does "compile", at least in a very lose sense, for optimization. In particular, one thing it does is move all of your variable declarations to the top of the current function. To give you a basic idea of what I'm talking about, here is my interpretation of the "compiled" version of the previous code sample:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  • // -------------------------------------------- //
  • // -------------------------------------------- //
  •  
  • var window;
  •  
  • // -------------------------------------------- //
  • // -------------------------------------------- //
  •  
  • // Log the window.
  • console.log( "Before: ", window );
  •  
  • // Overwrite the local window reference by create a
  • // local jQuery "window" wrapper for access to things
  • // like width() and scrollTop().
  • window = $( window );
  •  
  • // Log window.
  • console.log( "After: ", window );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

As you can see here, the Javascript interpreter (Me) has moved the "var" Window variable declaration up to top of the function and removed the "var" keyword form the actual variable assignment. The Javascript interpretor does this to allow our var usage to be easily sprinkled throughout our code without complicating scope-chain-crawl. Once it does this, however, you can quickly see that my local window reference is going to try to reference itself, rather than the global window object, in some sort of ill-fated mobius strip approach.

So now that we see why this is breaking, what can we do to create a local, jQuery-ized window object? The simplest way would be to create a reference to the window object outside of the document-ready event handler:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // Create a window-reference outside of the event handler.
  • // The event handler will create a closure to this scope,
  • // which will maintain this variable reference.
  • var thisWindow = window;
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  • // When the DOM is ready to be interacted with, initialize
  • // the scripts.
  • jQuery(function( $ ){
  • // Overwrite the window object, using the thisWindow
  • // proxy variable.
  • var window = $( thisWindow );
  •  
  • // Log window.
  • console.log( "After: ", window );
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

As you can see here, we've created a variable, thisWindow, which points to the global window object. When we then create the document-ready event binding, we pass an anonymous function off to jQuery. This anonymous function creates a closure to the global scope, granting the event handler access to the thisWindow variable during its execution. Now, since the variable assignment doesn't rely on a reference to "window", the locally-scoped window variable has no problems wrapping the thisWindow reference in a jQuery collection. And, when we run the above code, we get the following output:

After: [ Window window_in_ready.htm ]

Another solution to this problem would be to leverage the power of self-executing functions. In a self-executing function situation, we can create locally-scoped variables through input-argument translation. To see what I mean, take a look at this code:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Wrapping The Window In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // Create a self-executing function. The parameters to
  • // this function - window, $ - will create locally-scoped
  • // versions of the arguments being passed in.
  • (function( window, $ ){
  •  
  •  
  • // When the DOM is ready ......
  • $(function(){
  •  
  • // Log window.
  • console.log( "Local: ", window );
  •  
  • });
  •  
  •  
  • })(
  • jQuery( window ),
  • jQuery
  • );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Wrapping The Window In jQuery
  • </h1>
  •  
  • </body>
  • </html>

Here, we define the self-executing function's parameters as "window" and "$". These parameters then become our locally-scoped versions of whatever is being passed in during the self-executing method invocation. In our code, the following variable translation is taking place during said invocation:

jQuery( window ) .... becomes .... window

jQuery .... becomes .... $

As you can see, when we invoke the method, we use the global window object to create a nameless jQuery collection. This jQuery collection then gets assigned to the locally-scoped "window" parameter. I happen to really like this approach as it creates a very sexy and elegant encapsulation.

So much of Javascript is written in a top-down manner that it's very easy to forget that the code does get "compiled" to some degree (this may be even more true with the most recent browsers). When you're trying to do something tricky, like create a local reference of a globally available object, forgetting this fact can cause some serious head banging and hair pulling!

NOTE: I use the term "compiled" in the loosest possible manner.




Reader Comments

Feb 2, 2010 at 8:43 PM // reply »
4 Comments

Very elegant solution... and good reminder. It's always the little stuff that leaves you wanting to throw your monitor through the window !


Feb 2, 2010 at 8:46 PM // reply »
10,638 Comments

@Ryan,

Right?? Luckily they strap my wrists to the chair, so the monitors are safer :)


Jim
Feb 3, 2010 at 1:36 PM // reply »
8 Comments

I use the self executing lambda with params a LOT, but with window using self might be the simplest:

jQuery( function( $ ){ var window = $( self ); console.log( window ); } );


Feb 3, 2010 at 3:49 PM // reply »
10,638 Comments

@Jim,

Oh wow, I forgot that window had a "self" property (it's been forever since I cared about framesets). Dynamite drop-in.


Feb 4, 2010 at 8:58 PM // reply »
1 Comments

Nice solution, but you could simply use other identifier than 'window' for the wrapped window object and the problem would be solved. Since javascript has function scope, the local variable window overrides the global window object as soon as the function get executed.

jQuery(function($) {
console.log('before: %o', window);
var wrappedWindow = $(window);
console.log('after: %o', wrappedWindow);
});


Feb 4, 2010 at 9:05 PM // reply »
10,638 Comments

@Elvis,

Yes, very true; I just liked the idea of using the "window" name. Since it's used so often, it just felt natural. But again, that's just a personal preference issue - your approach is good as well.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »