17

A while ago, I offered-up a JavaScript design pattern (the Module Pattern - see below) that I got from a John Resig example as part of a solution to someone’s question and I received the following comment:

“…that pattern is a bit over engineered and not that good. Still leaking into global-scope. and your not opening yourself to async loaders. But it is better then just ad-hoc coding !”

So…

If “leaking” into global scope means “your object gets appended to the browsers window (object)”…then everything already gets appended (globally):

This “leaks” into global scope:

window.jQuery

…just call: window.jQuery and it resolves as a function();

This “leaks” into global scope:

function HelloWorld() { alert(‘Howdy’); }

…just call: window.HelloWorld() and you will get ‘Howdy’.

This “leaks” into global scope:

var myVariable = 10;

…just call: window.myVariable and you will get 10

If the commenter is correct, then all the above “leak” into global-scope. So, personally, I don’t see a way NOT to “leak” into global-scope as even your form controls exists there (as well).

As such, here are my questions…

  • What is meant by “leaking” into global-scope?
  • Why is that bad?
  • How do you avoid it?
  • When wanting to create persistent custom-objects, why is the Module Pattern (below) bad?
  • Design patterns let you encapsulate complex logic, is encapsulation suddenly bad simply because we’re writing in JavaScript?
  • Or...is this commenter simply wrong?

Here is the Module Pattern I Mentioned Above:

<script type="text/javascript">
    var myNamespace = (function($) {
        var publicInstances = {};

        // ***********************
        // myObject
        publicInstances.myObject = myObject;
        function myObject() {

            /// <summary>A pointer to this</summary>
            var self = this;

            this.someProperty = new String();

            this.initialize = function() {
                /// your code here
            }
            this.someMethod = function() {
                /// your code here
            }

            self.initialize();
        }

        return publicInstances;
    })(jQuery);


    jQuery(document).ready(function() {
        // Use would look like
        var myInstance = new myNamespace.myObject();
    });
</script>


UPDATED:
I’m satisfied with the answers below and want to thank everyone for taking the time to comment.

TO RECAP THE ANSWERS BELOW:
"Leaking" into global-scope occurs when something used in local-scope is unintentionally made available to the global-scope (e.g. the window object). This is bad because it opens the page to potential naming collisions which could result in variables resolving to unexpected values or types.

Intentionally making a variable global is not considered a "leak". However, properly namespacing the object is required to reduce potential for said naming collisions.

You cannot avoid globally-scoped variables, but you can reduce the above risks by using asynchronous-loaders and defining-modules made available in plug-ins like RequireJS or Curl.

Community
  • 1
  • 1
Prisoner ZERO
  • 12,996
  • 18
  • 80
  • 124
  • 3
    The second and third example don't "leak" into global scope if they are not defined in global scope. Maybe you also want to provide a link to the answer in question. – Felix Kling May 10 '11 at 14:03
  • I added the link above...but I would really like people to focus on answering THIS question rather than critiquing the source-question. – Prisoner ZERO May 10 '11 at 14:20
  • FYI: I'm going to let more people vote this before I mark answered – Prisoner ZERO May 10 '11 at 16:15
  • "You cannot avoid globally-scoped variables" - if you mean "you can't write JavaScript code that doesn't create at least one globally-scoped variable", I don't think that's true. (E.g. `(function () {var geoff='geoffdegeoff'; alert('local: ' + geoff);})(); alert('global:' + window.geoff);`). But I think you mean that in practice, most JavaScript you write will probably want to create at least one globally scoped variable. – Paul D. Waite Dec 21 '12 at 14:53

4 Answers4

6

"Leaking" into global scope is when something used in a local scope is unintentionally made available to the global scope. That means assigning to a variable not already defined in the current scope:

function myFunction() {
    a=1;
}

myFunction();
alert(a);
//-> 1

It's bad because there could be naming collisions resulting in variables with different values/types than expected. It can also lead to a bug in older Internet Explorers when you forget to use the var keyword for a variable used in a for statement.

I wouldn't class intentionally making a variable global as "leaking", because it's more like you're "pouring" it into the global scope. However, this is still often considered bad practice by some (although I think that's a little melodramatic) because there are still potential naming collisions with current properties of the window object, or variables set by other scripts and libraries.

Andy E
  • 311,406
  • 78
  • 462
  • 440
  • I agree with your use of the word "unintentional". In my opinion, I think ALL (persistent) custom-objects should be encapsulated (as shown). But I "could" be wrong...which is why I asked. – Prisoner ZERO May 10 '11 at 14:26
  • Can you clarify as to what you mean by 'pouring'? – Prisoner ZERO May 10 '11 at 14:27
  • @Prisoner: I just mean it in the way that "leaking" is something that happens unintentionally, "pouring" is something you do intentionally. Just trying to keep the liquid analogy going :-) – Andy E May 10 '11 at 14:29
  • 1
    yes that comment was melodramatic ;) globals are bad though! all of them. – Raynos May 10 '11 at 15:26
5

[[Short story]]

Don't make global variables ever and use an async module loader like requirejs or curl

[[Long story]]

That comment was poorly structured.

There is nothing wrong with the module system. I was complaining about using global variables at all. (I still think the full generic module pattern is bloated).

Whether you should avoid all global variables is a different question and I think a matter of style. You can either use an async loader to pass modules around or using window to pass modules around.

  • What is meant by “leaking” into global-scope?

What I meant was your creating global variables. Minimising the use of global variables is a pattern. In functional style programming it's possible to have zero global variables but this is a different pattern from using global modules.

  • Why is that bad?

Having any state globally can cause that state to be corrupted.

  • How do you avoid it?

You can't. You can minimize the amount of global variables though. To avoid having global state completely you can use asynchronous loaders. These define a few global variables for you that you can then use.

  • When wanting to create persistent custom-objects, why is the Module Pattern (below) bad?

There is nothing wrong with the module pattern. The problem is storing your module globally. The issue is having global namespaces.

  • Design patterns let you encapsulate complex logic, is encapsulation suddenly bad simply because we’re writing in JavaScript?

Now that I've cleared up the intent of the comment this question isn't really relevant

  • Or...is this commenter simply wrong?

The comment was poorly phrased at best. I objected to global namespaces rather than modules, but did not state this properly.

The alternative is using asynchronous loaders and defining modules. These can be narrowed down to two global variables. define and require.

require = function(moduleName, callback)

This will get a module and then return it to you.

define = function(obj)

this defines a module.

The concept here is that you multi file code as follows:

// main.js
require([
  "foo.js",
  "bar.js",
  ...,
], function(foo, bar, ...) {
   // do stuff
}); 

//foo.js

(function() {
    var namespace = modulePatternCode;
    ...
    define(namespace):
})();

//bar.js 

(function() {
    var namespace = modulePatternCode;
    ...
    define(namespace):
})();
Paul D. Waite
  • 89,393
  • 53
  • 186
  • 261
Raynos
  • 156,883
  • 55
  • 337
  • 385
  • +1 Using a module loader, everything (except the module loader itself perhaps) can be contained within closures. – Ates Goral May 10 '11 at 15:21
  • @AtesGoral yes. You need two global objects for the module loader. – Raynos May 10 '11 at 15:24
  • Would it be possible to post an example of a loader? – Prisoner ZERO May 10 '11 at 15:26
  • Aha! @Raynos was the culprit. That explains everything. – Andy E May 10 '11 at 15:35
  • 2
    @AndyE Oi! That's mean. @PrisonerZERO [requirejs](http://requirejs.org/) has the exact (more or less) syntax I mentioned. It also has a whole suite of support tools around using it as your async loader. – Raynos May 10 '11 at 15:43
  • @Prisoner: Here you go: http://stackoverflow.com/questions/5951228/what-is-meant-by-leaking-into-global-scope/5953226#5953226 – Ates Goral May 10 '11 at 16:27
1

Your module only "leaks" it's namespace holder so it's pretty acceptable.

Headshota
  • 19,673
  • 11
  • 54
  • 77
  • Actually, that is my impression as well. In my opinion, this is the essence of encapsulation. But...I "could" be wrong....which is why I'm asking. – Prisoner ZERO May 10 '11 at 14:19
  • the namespace holder should "leak", that's why it's used as a namespace. =) – Headshota May 10 '11 at 14:21
  • Yeah unless you hook yourself into an existing namespace I don't see how you can't "leak" at least one identifier. – Jad May 10 '11 at 14:31
  • that's why it's recommended to use style of naming like java does, using one's domain name e.g. com_mydomain . – Headshota May 10 '11 at 14:49
  • The namespace holder does not have to "leak", if you're using a module loader. Everything (except the module loader's own functions) can be contained inside closures. – Ates Goral May 10 '11 at 15:19
0

Loader example using RequireJS:

Define a utilities module in utils.js:

define(function () {
    return {
        each: function (iterable, callback) {
            // ...
        },
        map: function (iterable, mapper) {
            // ...
        }
    };
});

Use the above module in another module, say math.js:

define([ "utils" ], function (utils) {
    return {
        sum: function (numbers) {
            var sum = 0;

            utils.each(numbers, function (n) {
                sum += n;
            });

            return sum;
        },
        average: function (numbers) {
            return this.sum(numbers) / numbers.length;
        }
    };
});

And you can use math.js in another file, say main.js:

console.log("About to add 1-3");

require([ "math" ], function (math) {
    console.log(math.sum([ 1, 2, 3 ]));
});

You can still have namespaces, and still keep them warm and cozy inside modules:

namespace.js:

define([ "foo", "bar", "moo" ] function (foo, bar, moo) {
    return {
        foo: foo,
        bar: bar,
        moo: moo
    };
});

Then the rest of the modules can use this namespace during definition:

define([ "namespace" ], function (namespace) {
    namespace.foo(42);
});

Or at runtime, in some other module:

define(function () {
    return {
        initialize: function () {
            require([ "namespace" ], function (namespace) {
                namespace.foo(42);
            });
        }
    };
});

In the usages above, nothing but define and require are global. Of course, these are just illustrative examples, as there are many different flavors of defining/using modules in RequireJS.

Ates Goral
  • 126,894
  • 24
  • 129
  • 188
  • Remember that `callFoo` should be wrapped in a closure. You shouldn't have public functions in global scope either. – Raynos May 10 '11 at 17:38
  • @Raynos: That excerpt was meant to be from some module. I'll turn it into a module to make the point clearer. – Ates Goral May 10 '11 at 18:35
  • I know the meaning, simply pointing out it could be misinterpreted. I just wanted to clarify that you should avoid both global `var` statements and global function declarations – Raynos May 10 '11 at 18:40