Using 'window', 'document' and 'undefined' as arguments in anonymous function that wraps a jQuery plugin
Asked Answered
E

4

25

Honestly, I didn't know how to make the title shorter.

I learnt how to write a jQuery plugin by studying the source of SlidesJS plugin. When I encountered something new, I just asked my good friend Google and most of the times, got a satisfactory answer. Honestly though, I never made much effort. All I know is that $ is (probably) a shorthand jQuery object constructor and that $() and jQuery() are the same thing provided jQuery is included.

Recently, though, I tried to understand the science behind jQuery and how to write a good jQuery plugin. I came across a very good article in which the author listed several templates for creating a jQuery plugin. Since the rest were too complex for me to understand, I liked the first one: A Lightweight Start. Now, here is the code for the said template.

/*!
 * jQuery lightweight plugin boilerplate
 * Original author: @ajpiano
 * Further changes, comments: @addyosmani
 * Licensed under the MIT license
 */


// the semi-colon before the function invocation is a safety 
// net against concatenated scripts and/or other plugins 
// that are not closed properly.
;(function ( $, window, document, undefined ) {

    // undefined is used here as the undefined global 
    // variable in ECMAScript 3 and is mutable (i.e. it can 
    // be changed by someone else). undefined isn't really 
    // being passed in so we can ensure that its value is 
    // truly undefined. In ES5, undefined can no longer be 
    // modified.

    // window and document are passed through as local 
    // variables rather than as globals, because this (slightly) 
    // quickens the resolution process and can be more 
    // efficiently minified (especially when both are 
    // regularly referenced in your plugin).

    // Create the defaults once
    var pluginName = 'defaultPluginName',
        defaults = {
            propertyName: "value"
        };

    // The actual plugin constructor
    function Plugin( element, options ) {
        this.element = element;

        // jQuery has an extend method that merges the 
        // contents of two or more objects, storing the 
        // result in the first object. The first object 
        // is generally empty because we don't want to alter 
        // the default options for future instances of the plugin
        this.options = $.extend( {}, defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        this.init();
    }

    Plugin.prototype.init = function () {
        // Place initialization logic here
        // You already have access to the DOM element and
        // the options via the instance, e.g. this.element 
        // and this.options
    };

    // A really lightweight plugin wrapper around the constructor, 
    // preventing against multiple instantiations
    $.fn[pluginName] = function ( options ) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, 
                new Plugin( this, options ));
            }
        });
    }

})( jQuery, window, document );

I have included the comments so as to refer to them in my questions.

I have a crude idea why window and document have been included in the argument of the anonymous function that wraps the plugin (I don't know what else to call it) because it is given in the comments that it sorta kinda shortens the execution time. But how does that work? Any argument of the said anonymous function wrapping the plugin gets passed on to where? And how are these addressed in the plugin?

Normally, I would do $(window).resize(function(){}) but that doesn't work in this case. If I do console.log(window) inside the Plugin function, it says 'undefined'.

Which brings me to the other question which is: what is undefined? Isn't it a data type that is assigned to an object that isn't defined in the scope? How can it be passed as an argument? Don't the arguments have to be objects? There are a few lines written about this in the comments, but I don't understand a word of it: <so we can ensure that its value is truly undefined> whaaa?

To sum up:

  • What indeed is meant by function($)?
  • Why should I include window, document and undefined as arguments of function($)?
  • If I do it, how do I access the actual window and document objects?
  • undefined what, and why?

Please go easy on me. I never studied programming language as a subject for the express purpose of writing applications. I studied basic C for writing hardware oriented low-level routines for tiny core microcontrollers and that's just about it. I did learn C++ extensively and a bit of Java on my own. Just so you'd know what to expect.

Ere answered 3/4, 2013 at 1:48 Comment(2)
I've been writing javascript for the best part of two decades and some aspects are still a mystery. This particular use of undefined is a case in point. It eludes me, despite the author's attempted explanation.Dneprodzerzhinsk
possible duplicate of What advantages does using (function(window, document, undefined) { ... })(window, document) confer?Pique
P
23

When you write a function like:

(function (foo, bar) {
    return foo.getElementById(bar);
})(document, "myElement")

then the function is immediately called with arguments document and "myElement" for parameters foo and bar. Therefore, inside the function, foo.getElementById(bar) is equivalent to document.getElementById("myElement").

Similarly, in your plugin example, you are immediately calling the function with the arguments jQuery, document, window.

What indeed is meant by function($)?

The $ simply represents a reference to a jQuery object that is passed in to the wrapper function. Later, when the anonymous function is called with (jQuery, window, document), the $ reference inside the function references the jQuery object. This is done for a number of reasons, not least of which is that $ is quicker to type. It also allows the user to apply your plugin in wrapper to a particular instance of jQuery, produced perhaps by jQuery.noConflict().

Why should I include window, document and undefined as arguments of function($)?

You don't need to include these. The original author's reasoning is that assigning function-local variables to reference these will shorten the time it takes to resolve these variables. I assert that the savings are negligible; I personally wouldn't bother unless I used a lot of references to window and/or document.

As for undefined, the original author's purpose in including this is to ensure that someone hasn't altered the undefined global variable in EcmaScript 4 (edit: actually ECMAScript 3 -- version 4 never made it) or earlier. Again, I can't envision this problem cropping up. If you're truly worried that this could be a problem, just include something like this in your function:

if(typeof undefined !== "undefined") {
    undefined = void 0;
}

If I do it, how do I access the actual window and document objects?

All you have to do is make sure that the function call at the end of your anonymous function passes in the actual (jQuery, window, document) parameters. Or, don't include the window and document arguments in your function signature. Either way, you will be referring to the actual objects, regardless of the level of indirection.

undefined what, and why?

undefined is a global variable of type "undefined". Fields that have not been initialized are exactly equal (===) to undefined. It allows the programmer to differentiate between a deliberately null value and a simple uninitialized one. In ECMAScript 5 and later, undefined is read only. Prior to that, it is possible that other code could modify the value of undefined. You can always get the true value undefined with the expression void 0... as in myUndefinedVar = void 0;.

Pentameter answered 3/4, 2013 at 2:24 Comment(6)
You mean ECMAScript 3, ES4 was actually aborted. Other than that, nice answer!Dolmen
+1, BTW, it's impossible to tell the difference between a variable that has not been assigned any value and one that has been assigned a value of undefined.Herne
@Herne that's not true. For instance in a browser you can check window.hasOwnProperty('neverCreatedVariable') will return false, but window.hasOwnProperty('assignedVariableToUndefined') will return true. In a function you can do this.hasOwnPropertyBroderic
@Lorenz03Tx—the global execution context is the only one in which you can use hasOwnProperty to test for a variable, it can't be used to test for function scoped variables (it's not a general solution). The OP and answer are in a function execution context.Herne
@Herne , if you are in a function you can add the "use strict" and use a try block to catch the ref error. Not something one does generally but the point is there is a difference between variables assigned undefined and ones that are undefined and you can check for it if you so had the use case / desire to do so.Broderic
Long overdue, but this deserves to be the chosen answer. Not that any of the others were sub-par. I was going through my history when I saw that I had not selected an "accepted answer". You'll have to forgive my manners. I was an idiot back then (as is evident from my question). I'm a little bit less of an idiot now thanks to heroes like you. Cheers! :)Ere
H
3

What is meant by function($)?

Passing $ as an argument to your function makes it possible for you to use the jQuery shorthand object, which, as you clearly identified, is just an alias of the jQuery object (also take a look at jQuery.noConflict()).

Why should I include window, document and undefined as arguments of function($)? I have a crude idea why window and document have been included in the argument of the anonymous function that wraps the plugin (I don't know what else to call it) because it is given in the comments that it sorta kinda shortens the execution time. But how does that work? Any argument of the said anonymous function wrapping the plugin gets passed on to where?

Passing window and document as local variables kinda shortens the execution time because it is easier and faster to access a local variable than a global variable, because local variables are first in the chain. Which sort of answers your second question: the arguments get passed inside the anonymous function scope, which is the whole point of you making an anonymous function anyways: to create a closure.

Those arguments are passed at the end of your function, where you see the last parenthesis. The variable window in your closure refers to the global window because you passed it at the end.

The reason why you pass undefined is because undefined is a Javascript confusing mess. Basically, undefined is a property of the global object, that is, it is a variable, a singleton. It is not protected, that means that can be overridden. It means you could have an undefined which is actually defined.

By passing undefined you make sure that, even if anyone messed with undefined in the global scope (never trust the global scope! :)), you still get a correct undefined.

Also, the same performance reasons apply to undefined.

If I do it, how do I access the actual window and document objects?

You are already accessing them, because you are passing them to your function. And as such you are able to manipulate them.

Hellbent answered 3/4, 2013 at 2:18 Comment(0)
F
2

Before answering in detail let me note that unlike other programming languages, javascript is a bit odd for two reasons: first, it was created in haste this meant lots of things didn't get refined or implemented well. Second it was adopted by the internet very, very quickly and Microsoft copied the language very quickly and very accurately (including the bugs in the language). The result of this is that trying to correct mistakes in the design of the language was hard and/or impossible because they didn't want to break compatibility with existing websites.

Now to go into the details:

What indeed is meant by function($)

Nothing special. In javascript function and variable names are allowed to use the letters a-z including uppercase, the numbers 0-9 and the symbols $ and _. There are no other restrictions on how they can be used. Though there are guidelines and conventions, some mentioned by the language spec itself, some grew with the programming community.

Therefore, $ is just a variable name. There is no difference between:

function foo ($) {alert($)}

and

function foo (x) {alert(x)}

It's just the name chosen for the parameter. However, the spec strongly suggests that $ shouldn't be used by code written by humans. It's ok for computer generated code (a coffeescript compiler for example) but not ok for regular scripts. On the other hand it is strongly encouraged in scripts that use jQuery that $ always refer to the jQuery object (which incidentally happens to also be a function, that's fine in javascript since functions are objects).

Since you're writing jQuery the meaning of function ($) is an anonymous function that accepts one argument and the argument it expects is a jQuery object.

Why should I include window, document and undefined as arguments of function($)?

Arguably, one of the design mistakes in javascript is the lack of support for constants and/or immutable/protected variables/objects. As such, window, document and undefined are actually regular global variables - anyone can reassign them to anything.

The following is crazy but valid javascript code:

window = 42;

No sane programmer would do this but it's possible none the less. The jQuery developers were very concerned by this and so jQuery tries its best to pass the real window, document and undefined into plugins in case anyone is crazy enough to do crazy things.

One of the features of javascript is that function arguments override global variables. So if any of the above variables have been hijacked by someone else they are reassigned to their proper names in the function.

If I do it, how do I access the actual window and document objects?

You are expected to pass the correct window, document and undefined as arguments named window, document and undefined into the function. Doing anything else means that you no longer have access to the window, document and undefined objects.

There are crazy workarounds that you can do to try go grab a hold of the window object (also know as the global object) and from there you can get a hold of document. The jQuery code is actually one workaround to get back undefined in case it has been hijacked.

undefined what, and why?

As you correctly stated. undefined is the value javascript gives to things that are declared but have no values assigned to them. But in javascript undefined is just a global variable. If you don't touch it it initially has the value of undefined (circular logic, I know). But it can be modified:

undefined = 'boom!';

And henceforth all undefined variables will have the value of "boom!". The latest spec of the javascript language actually disallows reassigning to undefined but as of today only Safari does this.

Again, no sane programmer will do this.

Filmer answered 3/4, 2013 at 2:30 Comment(3)
Saying "function arguments override global variables" infers they change their values. Better to say they "shadow" them or similar jargon (unless you want to get into explaining identifier resolution on the scope chain).Herne
Please do get into explaining identifier resolution on the scope chain. :)Ere
@RobG: I find that when explaining to newbies in my team, using terms like "shadow" confuses them a bit. Over the years I just tell them that local variables override globals and so far all understand that simple statement more clearly than a more strictly correct one.Filmer
H
0

The inclusion of an identifier in a parameter list is effectively the same as declaring the variable in the function body, e.g.

function bar(foo) {
}

is equivalent to

function bar(foo) {
  var foo;
}

but of course you just do the first if you want to pass a value to foo.

The primary reason for doing:

(function($) {
    // use $ here instead of jQuery
}(jQuery));

is that when jQuery was released, Prototype.js had already been using "$" as an identifier for its main function for some time. The above pattern allows jQuery and prototype.js to be used in the same page, using "$" as an identifier for different things.

Passing in document and window is, at best, a micro optimisation that has little benefit. It offers no protection against them being assigned different values than expected. Just don't bother and use window and document inside the function as global identifiers.

Including undefined in the parameters and not passing it a value is not a sensible way of ensuring undefined really is undefined as it can still be affected if a value is accidentally passed. A safer way is:

(function() {
    var undefined;
    ...
}());

Now you are certain that undefined in the scope of the function really is undefined. Or if you want an assignment:

(function() {
    var undefined = void 0;
    ...
}());

But that is just extra typing.

Herne answered 3/4, 2013 at 2:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.